Open Source
July 28, 2015

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

Moritz Klack
@moklick
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.

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 its 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')],
};
Colors Of Europe
Interactive Data Visualization (Zeit Online)
Are you interested in a collaboration?
We are specialized in creating custom data visualizations and web-based tools.
Learn more

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 dont 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. Its 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
Further Reading
webkid logo
webkid GmbH
Kohlfurter Straße 41/43
10999 Berlin
info@webkid.io
+49 30 232 575 450
Imprint
Privacy