Creating our first hapi.js server

I mentioned earlier in the chapter that I like to approach code and examples with a problem solving, code-first approach. So let's try that here. Our aim is to create an initial server that can respond to requests and will be our first experience with hapi. Let's see what it takes to install hapi and get our first hapi server running. I assume that by this stage, you have Node.js installed; if not, the Node.js website explains how to do this quite well. I also assume you have some experience with the command-line interface of your OS of choice. So to start our hapi.js project, first we'll create a folder within which we will create our server, Open your command line and type the following:

$ mkdir hapi-hello && cd hapi-hello

This creates a directory called hapi-hello, and makes it the current directory. Next we'll use npm to create our project metadata using npm init. If you installed Node successfully, the npm command will also have been added to your command line/terminal. One of the reasons to run this any time you start a node project is that any Node modules you install will be installed into this directory instead of its parent directory, as is the npm algorithm for installing modules. This also creates a package.json file in your current directory with all the metadata from the npm init command. You should take a look at this now.

Installing hapi

Next we will install the hapi module for use with this project:

$ npm install hapi --save

The --save flag tells npm to store this dependency in our package.json file. This is quite convenient for sharing this code with others, so we only need to share the actual application code and this package.json file. For another person to then run this project, they would just navigate to this directory and run npm install, and it would install all the dependencies listed in the package.json file. No messing around with the configuration of any kind.

Our first hapi server

With that, let's look at the code involved in our first hapi.js server:

const Hapi = require('hapi');                           // [1]
const server = new Hapi.Server();                       // [2]
server.connection({ port: 1337, host: '127.0.0.1' });   // [3]
server.route({                                          // [4]
  method: 'GET',                                        // [4]
  path: '/',                                            // [4]
  handler: function (request, reply) {                  // [4]
    return reply('Hello World
');                      // [4]
  }                                                     // [4]
});                                                     // [4]
server.start((err) => {                                 // [5]
  if(err) {                                             // [5]
    throw err;                                          // [5]
  }                                                     // [5]
  console.log(`Server running at ${server.info.uri}`);  // [5]
});                                                     // [5]

Again, I won't cover every detail here, but will give you the general gist of what's happening. Hopefully, you've noticed some similarities and differences between this example and the vanilla Node example given previously. With reference to the numbers in the code example, let's examine each section:

  • [1]: We require the hapi module and store it in a variable Hapi, similar to importing the http module.
  • [2]: We create a server with the hapi module, which is, in fact, the only function of the hapi module.
  • [3]: Next we add a connection, which is a listener. We add the port and host configuration. This is quite similar to the listen function of the http server object, but here we configure it first, before we call listen or start our server. If no port is specified, a random port will be assigned.
  • [4]: A sample route is added. This is slightly different to the preceding example, as it will only respond to 127.0.0.1 with Hello World and a 404 error to all other requests, whereas the preceding node example would respond to any URL. We can, of course, easily update this example to do the same, but I will cover that in the next chapter. This is where we see the configuration versus code approach more clearly—here we configure the route options as opposed to using code with a configuration object.
  • [5]: Finally, we start the server that we have created. We also make sure to handle the error provided from the server.start() callback. This then ensures that all configuration is correct; if not, we want to throw the error so the server will not start and will send the error to the console, making it easy for us to debug.

What I hope is evident from the preceding example is that even though slightly more verbose, the hapi example is quite simple to follow and understand. All configuration is done when creating the server. Configuration is done when creating a route, which means well-defined handlers that can focus on just handling the request and not dealing with other parts of the transport layer.

To run this example, create a file in your current directory called index.js, and either type or copy-and-paste the preceding code. Then simply run the following command:

$ node index.js

If all goes as planned, you should see the following screen:

Our first hapi server

If you navigate to this URL, you should have Hello World returned to you. If all goes well, you will see the following screen:

Our first hapi server

Hopefully, you've been following the examples and creating this server yourself, so congratulations on building your first hapi server! Let's take a look at the configuration versus code here and see how it makes code easier to read, despite being a little more verbose. If you remember the listen method from our vanilla node example:

.listen(1337, '127.0.0.1')

You would have noticed that it uses parameters to pass in options such as port and host. In hapi, the equivalent is as follows:

.connection({ port: 1337, host: '127.0.0.1' })

This method takes a configuration object. This may not seem significant at the moment as these examples are quite trivial, but imagine when you have much more parameters which are Booleans, Integers, Strings, and so on—identifying what they are becomes a bit tougher. hapi is based around this concept of configuration objects making for much more readable code.

hapi plugins

Through its plugin API, hapi encourages a plugin-centric approach to development both, in the use of third party modules and also for your own features within your application. The plugin API makes it very easy to break an application into smaller plugins so that code is more manageable and readable. This is one of my favorite features of the framework, as it provides a convention for structuring your code in a scalable manner. We'll explore the plugin API in detail in Chapter 3, Structuring Your Codebase with Plugins, but I just want to introduce the concept, and show how to register a third party plugin here. Taking the preceding server example, it would be handy to have the server routing table display all routes on startup, and fortunately, there is a plugin for this called blipp.

If you explore hapi's plugins on the hapi plugin page, www.hapijs.com/plugins, you'll notice that a lot of the plugins have silly or non-descriptive names such as poop, joi, inert, h2o2, and so on. This is an attempt at creating competition for plugin implementation, for example joi; a model schema validation library could be named hapi-validator, but that would then be the standard validation library which doesn't encourage competition to build a competing schema validation library. The reason for silly names is an attempt to reduce the seriousness of enterprise development and make it more enjoyable for developers.

So let's look at an example of using a third-party plugin. Adding blipp to our example, we get the following code:

const Hapi = require('hapi');
const Blipp = require('blipp');                     // [1]
const server = new Hapi.Server();
server.connection({ port: 1337, host: '127.0.0.1' });
server.route({
  method: 'GET',
  path: '/',
  handler: function (request, reply) {
    return reply('Hello World
');
  }
});
server.register(Blipp, (err) => {                   // [2]
  if(err) {                                         // [3]
    throw err;                                      // [3]
  }                                                 // [3]
  server.start((err) => {
    if(err) {
      throw err;
    }
    console.log(`Server running at ${server.info.uri}`);
  });
});

With reference to the numbers in the comments in the preceding code example, let's examine each section now:

  • [1]: We require the blipp module. Don't forget to install blipp through npm (npm install blipp) if you're trying this yourself.
  • [2]: We register the plugin using server.register(). This is an asynchronous function. This is immensely useful for doing operations like initializing database connections on startup. An array of plugins can also be passed in here, which is also very useful for registering multiple plugins at once.
  • [3]: We handle the error callback here. If there's any error, we will just throw it, and since it is uncaught, it will end the script execution and send the output to the console, again making it easier to debug what might have caused the error.

If you run this example, you will get the following output, which is the server routing table generated by the blipp module:

hapi plugins

Don't worry if this seems complicated now—we are just introducing the concept here. We will explore the server.register() API in depth along with creating your own plugins in Chapter 3, Structuring Your Codebase with Plugins; for now it is enough to know that it exists.

It's interesting to note that all of hapi was in 'core' at the beginning, in one repo. But over time, the hapi team broke out what functionality it could into plugins, making the hapi core project quite small, and each separate module much more manageable. All these plugins can be viewed in the hapi.js GitHub organization. They cover everything from payload and query string parsing to route matching, serving static content, and smaller utility modules like blipp in the preceding example. This approach is also very good when building an application—start with things in the core, and push functionality to plugins as the application begins to grow.

hapi configuration

hapi also has a concept of cascading configuration, which it's good to be aware of. That is, configuration of higher-level objects like the server and connection can be overwritten at lower layers such as plugins and routes, where the configuration is only applied at that level, much like how styles in CSS are applied.

A good example of how this is useful is authentication. Where we add and configure authentication, we would want to apply it to all routes within a server, and would do so at the server level. Then for one route to have no authentication required, for example a login page, we would just configure authentication to not be required on a particular route's configuration object. This may sound complex and tough to grasp initially, but as you see more examples of this, it will become clearer. It is enough just to be aware of it for now.

The hapi style guide

I'd like to draw your attention to the style of the code used in the preceding example. It adheres to the hapi style guidelines, and so will all examples in this book, as much as possible. I encourage you now to read the full list of rules on the hapi style guide available at https://github.com/hapijs/contrib/blob/master/Style.md. You may find that you have some disagreements with this and that's fine, but the importance of a style guide is not the individual rules, but that all the code is uniform. This makes spotting bugs and messy code much easier, and leads to clear code throughout the codebase. If you look at the hapi source code or source of hapi modules that adhere to the source, you will see a perfect example of this.

One aspect of the style I'd like to draw attention to is the functions versus fat arrow (=>) usage. If you look back to the previous example, you will see both are used, which might look inconsistent. The pattern used here is that inline callbacks must use arrow functions, while other functions may use either the arrow syntax or the function keyword. I currently use the function keyword anywhere I am not using an inline callback so as to support the use of the this keyword inside the function body.

So now that we know what a hapi server looks like, and what the code should look like in keeping with the style guide, let's take a look at how to add functionality to this application in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.142.166.31