How to create a REST API with Hapi, Dogwater and Bedwetter

July 28th 2015 by Moritz

Creating a REST API is a common task in web development. I really like the ecosystem of Hapi, so today I want to show you how to use the Plugins Dogwater and Bedwetter to create a RESTful API. Dogwater integrates the Waterline ORM, which is also used by Sails.js. Bedwetter uses our Waterline models to automatically create RESTful handlers for your API routes.

In this post we will create a pizza REST API. You can find the source code of this example in our hapi-rest-starter repository on github. If you have any questions please feel free to write a comment or contact me on twitter.

Source

Getting started

Before we can start, we need to install some dependencies:

$ npm install --save hapi dogwater bedwetter blipp sails-disk shortid

Or just clone the repo:

$ git clone git@github.com:wbkd/hapi-rest-starter-simple.git
$ cd hapi-rest-starter-simple
$ npm install
$ npm start

After installing the dependencies we can write our basic Hapi setup.

index.js

var Hapi = require('hapi');

var server = new Hapi.Server();
server.connection({ port: 1337, host: 'localhost' });

server.start(function () {
    console.log('Server up and running at:', server.info.uri);
});

We just created a simple Hapi server that runs on port 1337.

The Database Model

As I said, we want to create a pizza API, so we need to create a Waterline model. In this example it’s a pizza model that has an id, a name and ingredients. The model will be used by Dogwater/Waterline to create the database API. I prefer shortIDs so we are using shortid for the id attribute. You could also skip the id attribute and Waterline will automatically generate unique IDs (it increments an integer value beginning from zero).

./models/pizza.js

var shortid = require('shortid');

module.exports = {
  identity: 'pizza',
  connection: 'pizzaDB',
  attributes: {
    id: {
      type: 'string',
      primaryKey: true,
      unique: true,
      defaultsTo: function() {
        return shortid.generate();
      }
    },
    name: {
      type : 'string',
      required : true
    },
    ingredients: {
      type : 'array',
      required : false
    }
  }
}

In the model specification you can do a lot of different things, like creating instance methods, hooking into the lifecycle or write your own validations.

Dogwater Options

Dogwater is just a kind of adapter so that you can use Waterline in your Hapi application. To use the Waterline ORM we have to define three things.

  1. Connections - Here we define the names of our connections and which adapters they should use
  2. Adapters - Here we define the name and type of the adapters we want to use
  3. Models - Our database models

For this example we are defining a connection called “pizzaDB” that uses the adapter “pizzaDisk”. The “pizzaDisk” adapter is using “sails-disk” to store our data. In order to use MongoDB for example, we just need to install “sails-mongo” and use it as our adapter \o/.

var dogwaterOptions = {
  connections: {
    pizzaDB : {
      adapter: 'pizzaDisk'
    }
  },
  adapters:{
     pizzaDisk : 'sails-disk'
  },
  models: [require('./models/pizza.js')]
};

The REST Routes

Bedwetter does not create the routes for you but only the route handlers. This comes in handy when you not want to use an automatically generated handler for a route you can just write it yourself. Bedwetter matches the path and the method to create the appropriate handler of the route. For this example we are going to create the following routes.

./routes/pizza.js

var bedwetterOptions = {}

module.exports = [{
  // return all pizza items
  path: '/pizza',
  method: 'GET',
  config: {
    handler: {
      bedwetter: bedwetterOptions
    }
  }
}, {
  // return a specific pizza by id
  path: '/pizza/{id}',
  method: 'GET',
  config: {
    handler: {
      bedwetter: bedwetterOptions
    }
  }
}, {
  // create a new pizza
  path: '/pizza',
  method: 'POST',
  config: {
    handler: {
      bedwetter: bedwetterOptions
    }
  }
}, {
  // udpate an existing pizza by id
  path: '/pizza/{id}',
  method: ['PATCH', 'POST'],
  config: {
    handler: {
      bedwetter: bedwetterOptions
    }
  }
}, {
  // remove a pizza by id
  path: '/pizza/{id}',
  method: 'DELETE',
  config: {
    handler: {
      bedwetter: bedwetterOptions
     }
  }
}];

As you can see we don’t have to configure Bedwetter. For this simple example we just pass an empty object as the options.

Loading Plugins

To use the Dogwater and Bedwetter plugins we are using the register method. As you can see we are also using the “blipp” plugin. It’s a tiny helper that prints out all existing routes at startup.

server.register([{
    register : require('blipp')
  },{
    register: require('dogwater'),
    options: dogwaterOptions
  },{
    register: require('bedwetter'),
    options: {} 
  }
], function (err) {
    if (err) { return console.log(err); }
    // register routes
    // start server
});

Putting it all together

Eventually our index.js file looks like this. We are creating a connection on http://localhost:1337, configuring dogwater, loading the plugins, registering the routes and starting the server. You can find the source of this example in our hapi-rest-example repository.

index.js

var Hapi = require('hapi');

var server = new Hapi.Server();
server.connection({ port: 1337, host: 'localhost' });

var dogwaterOptions = {
  connections: {
    pizzaDB : {
      adapter: 'pizzaDisk'
    }
  },
  adapters:{
     pizzaDisk : 'sails-disk'
  },
  models: [require('./models/pizza')]
};

server.register([{
    register : require('blipp')
  },{
    register: require('dogwater'),
    options: dogwaterOptions
  },{
    register: require('bedwetter'),
    options: {} 
  }
], function (err) {
    if (err) { return console.log(err); }

    server.route(require('./routes/pizza'));

    server.start(function () {
      console.log('Pizza API up and running at:', server.info.uri);
    });
});

If you run your server with node index.js you should see the output of blipp:

http://localhost:1337
GET    /pizza                         
POST   /pizza                         
GET    /pizza/{id}                    
POST   /pizza/{id}                    
PATCH  /pizza/{id}                    
DELETE /pizza/{id}                    

Pizza API up and running at: http://localhost:1337

Now you can start using your REST API. You could use Postman or some curl commands to test it.

Examples:

Create a new pizza:

$ curl --data "name=Tuna&ingredients=tuna,cheese,tomatoes" https://localhost:1337/pizza

Get all pizzas:

$ curl http://localhost:1337/pizza
comments powered by Disqus