This tutorial uses the "Sample hapi.js REST API" project.
Take a look at: https://github.com/agendor/sample-hapi-rest-api/
##Topics
- Introduction
- Installing Node.js
- Installing MySQL
- Setting-up the project
- Folder and file structure
- The API
- The Server
- Authentication
- Routing
- The Controller
- Server Plugins
- The Model
- Validation
- Data manipulation
##Introduction
hapi.js is an open source rich framework built on top of Node.js for building web applications and services. Was created by Eran Hammer - currently Sr. Architect of Mobile Platform at Walmart - and powers up all Walmart mobile APIs. It was battle-tested during Walmart Black Friday without any problems. They also plan to put hapi.js in front of every Walmart ecommerce transaction.
In this tutorial, we'll use an open source project called sample-hapi-rest-api.
##Installing Node.js
The first thing we need to do is install Node.js. Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices. To install Node.js, open a terminal tab and run the following commands:
sudo apt-get update
sudo apt-get install nodejs
##Installing MySQL
In this project, we consume a MySQL database, if you don't have MySQL installed in your computer, you may install it by running the commands:
sudo apt-get update
sudo apt-get install mysql-server
sudo apt-get install mysql-client
During the installation process you will be prompted to enter a password for the MySQL root user.
##Setting-up the project
Ok, now we've installed Node.js and MySQL, we're going to setup the project. With this project, you can build a production-ready RESTful API that consumes a MySQL database built with hapi.js framework.
Follow the instructions presented in the project page: https://github.com/agendor/sample-hapi-rest-api
After setting-up the project, continue to the next sections.
##Folder and file structure
To begin with, the important parts of the structure for now are:
package.json
index.js
node_modules/
src/
|-config/
|-constants.js
|-controllers/
|-dao/
|-middleware/
|-basic-auth.js
|-db.js
|-models/
|-routes/
|-util/
|-validate/
- package.json: Holds project configuration.
- index.js: The starting point for the API.
- node_modules/: All modules described in
package.json
will be automatically placed here usingnpm
commands such asnpm install mysql --save
. - src/: Our source code base folder.
- src/config/: Application level configuration files.
- src/config/constants.js: Application level configuration constants.
- src/controllers/: Controllers modules/files.
- src/dao/: Data Access Object modules/files.
- src/middleware/: Holds modules/files that deals with different code environments.
- src/middleware/basic-auth.js: Our Basic Authentication strategy module. We'll see it latter.
- src/middleware/db.js: Abstracts our database initialization and manipulation.
- src/models/: Modules/files abstraction of our database schema.
- src/routes/: Modules/files that know which controllers should handle the incoming requests.
- src/util/: Help us with mixins methods.
- src/validate/: Knows how the incoming request should behave.
##The API
In every Node.js project, we have to have a starting point. In this project, the starting point is the index.js
file, which the main parts are:
//1.
var Hapi = require('hapi');
//2.
var server = Hapi.createServer(host, port, options);
//3.
for (var route in routes) {
server.route(routes[route]);
}
//4.
server.start();
- Assign hapi.js module to a
Hapi
variable. The module is present in thenode_modules
folder, so we don't need to specify a path for that. - Creates a server instance.
- Dynamically adds all the routes (end-points) to the server instance. The routes are stored in the
src/routes
folder. - Starts the hapi.js server.
##Authentication
It's very common in an API to validate the client's request against some credentials. In this tutorial, the credentials will be a user email and password stored in our MySQL database table user
. For this part, we are using hapi-auth-basic
module, so when the client makes a request, this module will handle the authentication strategy. Continuing in the index.js
file, we've added the hapi-auth-basic
to a server pack. A server pack groups multiple servers into a single pack and enables treating them as a single entity which can start and stop in sync, as well as enable sharing routes and other facilities (like authentication). The following code in index.js
is responsible for this assignment:
var basicAuth = require('src/middleware/basic-auth');
...
server.pack.require('hapi-auth-basic', function (err) {
server.auth.strategy('simple', 'basic', true, {
validateFunc: basicAuth
});
});
Look that the basicAuth
handler is a module/function stored in src/middleware/basic-auth
that we've created to validate the request credentials sent by the client.
This goes to the database and lookup for a user with the email and password informed.
You can test that by accessing: http://localhost:8000/tasks (if you haven't changed the port configuration), and typing:
- Username: [email protected]
- Password: 123
##Routing
The routing with hapi.js is pretty practical. Take a look at the file src/routes/task.js
. You'll see that we've configured all the routes (end-points) our task module needs with a list of JSON objects. Taking the first route as an example:
method: 'GET',
path: '/tasks/{task_id}',
config : {
handler: taskController.findByID,
validate: taskValidate.findByID
}
- method: the HTTP method. Typically one of 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'. Any HTTP method is allowed, except for 'HEAD'.
- path: the absolute path used to match incoming requests (must begin with '/'). Incoming requests are compared to the configured paths based on the server router configuration option. The path can include named parameters enclosed in {} which will be matched against literal values in the request as described in Path parameters.
- config: additional route configuration (the config options allows splitting the route information from its implementation).
- config.handler: an alternative location for the route handler function. Same as the handler option in the parent level. Can only include one handler per route.
- config.validate: is used to validate the incoming requests. We'll go deep later.
##The Controller
The controller modules are used to mediate the communication between requests and data manipulation. Open the src/controllers/task.js
file. See that we've implemented a method to handle each type of request routed by the src/routes/task.js
route. Every controller module will use a helper module called ReplyHelper
(src/controllers/reply-helper.js). Here is nothing much about hapi.js.
###Server Plugins
Reopen the file src/controllers/task.js
and look at the part:
findByID: function findByID(request, reply) {
var helper = new ReplyHelper(request, reply);
var params = request.plugins.createControllerParams(request.params);
taskDAO.findByID(params, function (err, data) {
helper.replyFindOne(err, data);
});
}
See that we are calling the createControllerParams
method from the namespace request.plugins
?
Plugins provide an extensibility platform for both general purpose utilities such as batch requests and for application business logic.
This method was declare in index.js
as:
server.ext('onRequest', function(request, next){
request.plugins.createControllerParams = function(requestParams){
var params = _.clone(requestParams);
params.userId = request.auth.credentials.userId;
return params;
};
next();
});
So it's basically a better way to say that every request object should be able to add the ID of the authenticated user to some requestParams
object and return it to the caller. We'll use this ID to make some queries to MySQL.
##The Model
As we are building an API to consume a MySQL Database, we need a place to store the same schema as defined in the database tables. The place for that are the Model
modules. Open up src/models/task.js
and take a look, we'll see how to make sure our Task objects have the right values in its properties.
##Validation
If you haven't read the previous section, open the src/models/task.js
file:
"use strict";
var _ = require('underscore');
var Joi = require('joi');
function TaskModel(){
this.schema = {
taskId: Joi.number().integer(),
description: Joi.string().max(255)
};
};
module.exports = TaskModel;
We are using Joi
module. As described in its repository, Joi
is:
Object schema description language and validator for JavaScript objects.
Every request we receive in our end-points is validated using Joi
. So every resource have a ResourceValidate
module (look at src/validate/task.js
which describes a validation schema for every route/method/end-point.
##Data manipulation
There is a very good module created by Felix Geisendörfer to manipulate MySQL
databases. It's called node-mysql
and we use it in our project.
If you take a look at src/dao/task.js
, you'll see that we have a corresponding method for every end-point a client can request. There is nothing about hapi.js
in here :)
##Conclusion
As we started to build the API for http://www.agendor.com.br, we couldn't find an example of files and folders structure, using the hapi.js
framework and consuming a MySQL
DB. So we've built our own, looking at lots of different Node.js
projects.
We are a case of migrating from PHP
to Node.js
to place an API in front of our already existent MySQL
database. We hope this project can serve as a starting point for many other teams that find themselves at this same cenario.
##References
- Eran Hammer presents Hapi.js 2.0 - http://vimeo.com/85799484
- Hapi, a prologue - http://hueniverse.com/2012/12/hapi-a-prologue/#more-1566