Developing Node.js applications with Express is no doubt a very popular option, however it isn’t the only option and it may not even be the best option. I recently started looking into Hapi which defines itself as being a framework for services, something that wasn’t exactly a thing when Express came around.

Previously I had written about creating a RESTful API with Node.js and Express, but this time around we’re going to explore doing the same with Hapi.

Before we get too invested in the code, we need to figure out what exactly we’re going to be building. The goal here is to create a two endpoint application. One endpoint should allow us to create data in the database and the other should allow us to read from the database.

Create a new project directory somewhere on your computer and execute the following command from the command prompt within that directory:

npm init -y 1 npm init - y

The above command will create a new Node.js project by establishing a package.json file. The next step is to install each of the project dependencies.

Execute the following from the command line:

npm install couchbase hapi joi uuid --save 1 npm install couchbase hapi joi uuid -- save

The above command will download the Couchbase Node.js SDK, the Hapi framework, Joi for data validation, and a package for generating UUID values to represent our NoSQL document keys.

Go ahead and create an app.js file within your project. This is where we will have all of our routing information and database logic. Open this app.js file and include the following JavaScript code:

const Hapi = require("hapi"); const Couchbase = require("couchbase"); const UUID = require("uuid"); const Joi = require("joi"); const server = new Hapi.Server(); const N1qlQuery = Couchbase.N1qlQuery; const cluster = new Couchbase.Cluster("http://localhost"); const bucket = cluster.openBucket("default", ""); server.connection({ "host": "localhost", "port": 3000 }); server.route({ method: "GET", path: "/", handler: (request, response) => { return response("Hello World"); } }); server.start(error => { if(error) { throw error; } console.log("Listening at " + server.info.uri); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const Hapi = require ( "hapi" ) ; const Couchbase = require ( "couchbase" ) ; const UUID = require ( "uuid" ) ; const Joi = require ( "joi" ) ; const server = new Hapi . Server ( ) ; const N1qlQuery = Couchbase . N1qlQuery ; const cluster = new Couchbase . Cluster ( "http://localhost" ) ; const bucket = cluster . openBucket ( "default" , "" ) ; server . connection ( { "host" : "localhost" , "port" : 3000 } ) ; server . route ( { method : "GET" , path : "/" , handler : ( request , response ) = > { return response ( "Hello World" ) ; } } ) ; server . start ( error = > { if ( error ) { throw error ; } console . log ( "Listening at " + server . info . uri ) ; } ) ;

The above code will get us started. It imports each of our project dependencies, initializes Hapi for a specific host and port, and establishes a connection to Couchbase. We have also defined a single route to represent our root route.

For this example, Couchbase will be running locally and we’ll be using a Bucket called default . For information on installing Couchbase, check out my tutorials for Mac, Linux, and Windows.

Now we can worry about our two endpoints that interact with the database.

The first and probably simplest endpoint will be for returning a list of documents, in this case people that had previously been created:

server.route({ method: "GET", path: "/people", handler: (request, response) => { var statement = "SELECT `" + bucket._name + "`.* FROM `" + bucket._name + "` WHERE type = 'person'"; var query = N1qlQuery.fromString(statement); bucket.query(query, (error, result) => { if(error) { return response(error).code(500); } return response(result); }); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 server . route ( { method : "GET" , path : "/people" , handler : ( request , response ) = > { var statement = "SELECT `" + bucket . _name + "`.* FROM `" + bucket . _name + "` WHERE type = 'person'" ; var query = N1qlQuery . fromString ( statement ) ; bucket . query ( query , ( error , result ) = > { if ( error ) { return response ( error ) . code ( 500 ) ; } return response ( result ) ; } ) ; } } ) ;

Notice the handler method. In it we construct a N1QL query that obtains all documents that have a type property that matches person . This means that we can have plenty of other document types that won’t be picked up by our query.

If there is a problem with the query, we will return an error with a 500 response code, otherwise we’ll return the query results.

The next endpoint is where we make use of Joi for data validation. Check out the following JavaScript code:

server.route({ method: "POST", path: "/person", config: { validate: { payload: { firstname: Joi.string().required(), lastname: Joi.string().required(), type: Joi.any().forbidden().default("person"), timestamp: Joi.any().forbidden().default((new Date()).getTime()) } } }, handler: (request, response) => { bucket.insert(UUID.v4(), request.payload, (error, result) => { if(error) { return response(error).code(500); } return response(request.payload); }); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 server . route ( { method : "POST" , path : "/person" , config : { validate : { payload : { firstname : Joi . string ( ) . required ( ) , lastname : Joi . string ( ) . required ( ) , type : Joi . any ( ) . forbidden ( ) . default ( "person" ) , timestamp : Joi . any ( ) . forbidden ( ) . default ( ( new Date ( ) ) . getTime ( ) ) } } } , handler : ( request , response ) = > { bucket . insert ( UUID . v4 ( ) , request . payload , ( error , result ) = > { if ( error ) { return response ( error ) . code ( 500 ) ; } return response ( request . payload ) ; } ) ; } } ) ;

When a client tries to consume from this endpoint, validation happens as part of Hapi. In this validation process we inspect the payload and make sure each of our properties meet the criteria. In this case both firstname and lastname need to be present. It is forbidden for a type and timestamp property to exist in the request body. If they exist, and error will be returned. If they do not exist, a default value will be used.

If our validation passes, we can insert the data into Couchbase with a unique id. The data will also be returned in the response.

Conclusion

You just saw how to create a very simple RESTful API with Hapi and Couchbase Server. I’ve been using Express since the beginning, but when it comes to Hapi, I feel like it was much better designed for creating web services. However, both will get the job done and if you’d like to see an Express alternative, check out this previous tutorial I wrote.

For more information on using Couchbase with Node.js, check out the Couchbase Developer Portal.