Shreyansh Pandey
This tutorial is out of date and no longer maintained.
As the century dawns, we see new technologies and architectures that companies, startups, and developers, alike, are using to power their next big application; some of these architectures have been so thoroughly battle-tested, that some companies are even scrapping old applications just so that they can implement this modern, new (and scalable) approach.
One of these architectures is the Representational State Transfer or REST as the people call it. In this architecture, the server talks in terms of resources, and then uses HTTP verbs to perform actions on those resources. A quick detour of this architecture has been taken in the following section.
With Node.js, it’s no doubt that it’s super-easy to implement a quick RESTful API and be up and running in no time; however, there are quite a few considerations before someone thinks of implementing a scalable, high-efficiency RESTful API.
This article builds on my previous article on Hapi.js. In case you find this confusing, please refer to the Part 1 of this tutorial. Once you’re through that one, you can come here and complete the entire series.
Honestly, I am a HUGE fan of the Hapi.js framework, and the Hapi.js community. Cheers to all those who have contributed to this remarkable project.
Hapi.js makes things so simple, and so smooth to work with; all that without compromising on the reliability or efficiency of the framework. In case you haven’t read it yet, I have a small article here which, as mentioned earlier, covers just enough material to get your feet wet and kicking for this project.
Now, before we start our work on this API, let’s set a couple of goals. Here the word ‘goals’ is like the success criterion of your application. Since this is a project to help you get up to speed with Hapi.js, we’ll really add the features which are unique: display or implement some of Hapi.js’ philosophy or concepts. It’ll also trim the deadwood from our experimental code-base.
This API is the Developer API for a startup that returns the birda spotted in a particular area.
I presume everything in this section is quite self-explanatory. However, if you are confused about something, feel free to drop us a comment in the comments section of this post.
In a RESTfully
-architectured web application, you let the HTTP verbs (like GET
, POST
, etc.) do the work of telling your application what to do. For this tutorial, we’ll have routes something like the following:
http://api.app.com/birds
(GET
) - to get a list of all the public
birdshttp://api.app.com/birds
(POST
) - to create a new birdhttp://api.app.com/birds/:id
(GET
) - to get a specific birdThis is something quite standard of any RESTful API. There is a pretty good post on Scotch about designing APIs with RESTful architecture in mind. Take a look.
With everything said, let’s dig right into it. To test the API, I will be using this fantastic application called Paw. Alternatively, you can use ARC or Postman App.
To create this awesome API, we’ll be using a couple of very interesting Node.js packages.
Knex.js
Knex is a very simple to use, yet incredibly powerful query builder for MySQL and a plethora of other RDBMS. We’ll use this to directly communicate with our Authentication and Data servers running MySQL.
Hapi.js
Hapi (pronounced “happy”) is a web framework for building web applications, APIs, and services. It’s extremely simple to get started with and extremely powerful at the same time. The problem arises when you have to write performant, maintainable code. You can take a look at my getting started tutorial to get started with it, and then this tutorial as a continuation.
Alright, perfect. Now we understand the nuances of this application and we can begin coding.
package.json
Initialize a new package.json
file with npm init
in your root folder and then filling the values as required. The following is my package.json
:
Now, let’s install mysql
, jsonwebtoken
, hapi-auth-jwt
, and knex
with
Note that this configuration is based on the configuration of my previous article. I haven’t included everything and this is just a build-up on that. Be sure to follow that one before this.
Now, our package.json
looks something like this:
And the following is the directory structure:
Perfect. Now, let’s configure Knex so we can begin working with it.
Knex
is just brilliant. And we will see the reasons for that brilliance in just a minute. Start by installing the knex cli
with sudo npm install -g knex
. This tool allows us to programatically create a MySQL table structure and then execute it. Generally, we call this migrations
.
For the rest of the tutorial, the following is my MySQL configuration:
MySQL Host: 192.168.33.10
MySQL User: birdbase
MySQL Pass: password
MySQL DB Name: birdbase
Create a knexfile.js
in the root of the directory with the following content:
And create a new folder called seeds
in the root directory. The knexfile.js
is used by the Knex CLI to perform SQL operations. The seeds
directory will contain our seeds
or initial data which we can use for testing. Trust me when I say this, having this at hand, greatly simplifies development as you already have the data you want to work with.
The structure should look something like the following:
Let’s create the actual migrations now. We’ll create two tables users
and birds
. The users
table will contain the username, password, name, and email of the users; and the birds
table will contain the listings of birds.
Create a new migration with knex migrate:make Datastructure
to create a new migration file. It’ll look something like 20161211185139_Datastructure.js
. In your favorite text editor, open it and you’ll see something like:
The up
function is executed when you migrate a database for this, and the down
function is executed when you roll back.
Add the following to the up
function:
The chain .references(...)
is used to create a composite primary key. This is done to ensure that we know who owns which listing.
In the down
function, add the following:
Remember to drop a referencing table first; i.e., a table that uses field-referencing. In this case, birds
had referenced guid
in the table users
, and so, we remove birds
before we remove users
as doing it the other way round will throw an error.
Let’s run this migration with knex migrate:latest
.
If all goes well, you should see:
Now, if you head over to phpMyAdmin
and check your database, you should see something like the following:
Looks great to me. We’ll now create some seed files by running knex seed:make 01_Users
. Remember that the seed files are executed in the order of their file name, and so, if you execute the seed for the birds
table first, the key constraint (reference) to guid
in user
will fail because the latter doesn’t exist.
Under the seed
folder, you should now see a new file titled 01_Users.js
; open it, and replace the code with the following:
The code is self-explanatory, so I wouldn’t bother going deeper. Similarly, let’s create a sample bird migration with: knex seed:make 02_Birds
and replacing the file with the following code:
Execute these seeds with knex seed:run
and then open phpMyAdmin
to be amazed.
Beautiful. Now we can move onto creating the actual API.
JWT Authentication
The authentication provider. In this case, we’ll be using the super-simple and secure JSON Web Token strategy for authentication and authorization. Before moving forward, we need to do JWT-101
.
A JWT is in the form xxxxx.yyyyy.zzzzz
with each of the sections having a specific name. We’ll consider the token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VybmFtZSI6ImxhYnN2aXN1YWwiLCJzY29wZSI6ImFkbWluIiwiaWF0IjoxNDgxMzg0NDQ3LCJleHAiOjE0ODEzODgwNDd9.
Y7B8rvGNmkwrSWMlb5e1Bqz0qnLuDLxerZmmdtg8ouo
The first block (xxxxx
) is the header of the token and contains metadata such as the algorithm used for signature, etc. In our token, on decoding with UTF8
encoding, we get the following content
The next block (yyyyy
) is the payload of the token and contains claims
, expiration and creation metadata
. Technically speaking, you can have whatever you want in this section. For our example, we get the following decoded content
As you can make out, this token was created for the user example
who has admin
scope and expires in 1h
. Simple.
The last block (zzzzz
) is the signature of the token and is calculated using HMAC256( ( base64Encode( header ) + '.' + base64Encode( payload ) ), secretKey )
. You define the secret on the server; the library (jsonwebtoken
) signs and verifies the tokens using this secret
. No matter what happens, make sure you do not leak this token anywhere as it will cause a problem in the authentication framework of your application.
A great tool to interactively debug and learn about JWT is its official website at JWT and the token debugger here. Below is a screenshot of the interactive JWT debugger in action.
Setting Up JWT
Before we do anything, we need to tell hapi
that we’re going to use an authentication strategy (method) and so, it should load a couple of modules. Open up server.js
and enter the following just after server.connection(...
Here, we ask the server
object to register a new module from the package hapi-jwt-auth
; afterwhich, we register a new authentication strategy called token
with the jwt
scheme and the following options:
key
- this is the private key that is used to sign and verify the JWT signatures;verifyOptions
- we tell the library which algorithm to use for signature and verification; HMAC256
in this case.You can also add validateFunc
to the block which is used to validate the token
provided. This is optional and is used when you have to do some other sort of verification in addition to the cryptographic verification provided by the library.
src/server.js
should look like the following now
Now, we can add the routes. Let’s start by adding a simple route which gets all the public birds. Within src/server.js
add the following route:
Now, let’s create a knex
instance. Add a file knex.js
within src
and add the following code to it:
Then import this file into your server.js
by import Knex from './knex';
. Now we are ready to utilize this awesome library.
Let’s select the name
, picture_url
and species
for every public
bird. In the handler for your route, add:
The line = Knex('...
tells Knex to use the birds
database, and then builds the query where
the field isPublic
is set to true
. Then fetches it using .select
(which returns a promise) and then resolving the promise. The parameter results
is an array of all the birds which match the criterion.
Save the file and start the API server with npm start
and then fire up your favorite API client. We’ll use Paw.
Pat yourself on the back if everything works as expected. Your src/server.js
file should look like this:
Now, we’ll continue by adding an auth
route which will be used to authenticate the user. The logic here is very simple: check if the password
of the payload is the same as the one in the database, and if so, create a new JWT token with the scope of the user’s GUID
which expires in 1h
.
While updating a bird
, we’ll use a preRouteHandler
to check if the current user owns the bird; if he does, then we’ll allow the edit, otherwise, we’ll throw a 403
error.
Let’s create a POST
route with
The line const { username...
decomposes the request.payload
object and gets the named values (username
and password
in this case). This is the same as:
Just another lovely example of why I love ES6.
Remember that the object request.payload
contains all the content in a POST
or a PUT
request.
Let’s continue.
First we need to make sure that we select exactly one. Let’s use the array deconstructor and then check if it’s populated:
Simple enough. We check if the user exists and if not, we throw an error and exit out of the function. Let’s finish this route:
The function jwt.sign( payload, key, [ options ] )
signs the payload and gives a JWT which we then transmit to the user. Save this file and start your server, and add a new request to your client.
Try changing the username and the password to see what kind of response you get.
So far, so good. Now we’ll add a method to create a bird, and a method to update a bird. Let’s start.
Create a POST
route at /birds
and add the empty handler function.
We expect to have a payload as bird
which is an object containing all the information about the bird. Let’s add the code to insert this into our database.
Before anything, we need to tell Hapi.js that this route is protected by authentication. To do so, add the following after method
in the route:
This tells Hapi.js that we’ll be using a registered authentication strategy for our route.
But, for this to work properly, we need to refractor the code a little bit. Let’s add a new file routes.js
containing all the routes. Something like:
Then, in src/server.js
, we’ll import the routes array as import routes from './routes';
and then within server.register(...
we’ll add the following bit to register all the routes:
The src/server.js
file becomes something like:
With that done, let’s move on.
Now, we need to add a bird
to the birds
database. We’ll do this using Knex
. Add the following bit immediately after const { bird } = request.payload;
in your route within src/routes.js
.
We’ll install a package to generate GUID’s called node-uuid
with npm i --save node-uuid
and then import it into our routes file as import GUID from 'node-uuid';
.
The code is pretty self-explanatory apart from this interesting object request.auth.credentials
. Well, after the verification is done, the authentication handler passes on the decoded token to this credentials
object. If you do a console.log( request.auth.credentials );
you’ll see something like:
From here, we can grab on to the GUID
for the user and pass it on as the owner
in the database. Simple.
Fire up the server and then add a couple of requests. Remember to add the Authorization
header in the following format: Authorization: Bearer <JWT>
where <JWT>
is your generated JWT in the /auth
route.
Let’s check the database:
And now let’s get a listing of all public birds:
With that in place, we can create our last route: PUT
at /birds/:guid
where we can update the bird with the GUID guid
. I’ll just copy and paste the POST
route and make the changes as and when required.
For starters, let’s change the method
to PUT
and the route
to /bird/{birdGuid}
. We can access this birdGuid
from request.params
. After the changes, it should look something like:
Now, we want to verify that the current user has rights to the bird he’s trying to edit. For that, we need to validate if the bird associated with birdGuid
has the same owner
as the scope of the authorization token passed. As illustrated before, we can access the token’s GUID
from request.auth.credentials
and then the scope
property in that. However, let’s add a route prerequisite: this function has the same signature as the handler
of a route and is executed before the control is passed to the handler
. Really useful in cases like this where you need to do some sort of verification. Also note that we can safely assume that when this function is being executed, some user has passed some valid JWT in the authorization header; had they not done that, the hapi-jwt-auth
library would’ve thrown a 401
error.
The route should be something like:
The pre
configuration block is an array which contains objects with a key method
which is linked to a function.
We’ll first pull out all the values from the objects and store them:
Next, let’s do a select
operation on the database where we select the bird from the GUID
provided; we’ll just select the owner
column of the bird as that’s all we need for verification:
Brilliant. Now, we have selected just one bird with [ result ]
and we can work on that.
Let’s start by verifying that we actually have a bird with the specified GUID; if we do not, then we’ll take over the request and send a custom reply. reply().takeover()
ends the reply chain with the last response you give, and hence, does not let the handler
get envoked.
Next, let’s check if the scope
of the current token allows the user to modify the bird with the guid
.
If not, we’ll just let the reply
chain continue:
After you’re done, this method should be something like:
Lastly, let’s add the update
chain to the handler
so we can UPDATE
the current bird with the information.
The code is self-explanatory. So, we’ll just continue. Now, fire up the server and add a new request.
Let’s change the isPublic
parameter:
You can also try giving incorrect birdGuid
and seeing what happens:
In the end, we learned quite a few things here. We went from just being a beginner in Hapi.js to creating a fully blown API in Hapi with Authentication, MySQL DB, etc. The code is available here; if you find some errors, or have some suggestions, please be sure to tell me. if you have any questions, be sure to throw them in the comment box below. Until next time! Cheers!
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!