A Side-by-side Comparison of Express, Koa and Hapi.js

    Olayinka Omole
    Share

    If you’re a Node.js developer, chances are you have, at some point, used Express.js to create your applications or APIs. Express.js is a very popular Node.js framework, and even has some other frameworks built on top of it such as Sails.js, kraken.js, KeystoneJS and many others. However, amidst this popularity, a bunch of other frameworks have been gaining attention in the JavaScript world, such as Koa and hapi.

    In this article, we’ll examine Express.js, Koa and hapi.js — their similarities, differences and use cases.

    Background

    Let’s firstly introduce each of these frameworks separately.

    Express.js

    Express.js is described as the standard server framework for Node.js. It was created by TJ Holowaychuk, acquired by StrongLoop in 2014, and is currently maintained by the Node.js Foundation incubator. With about 170+ million downloads in the last year, it’s currently beyond doubt that it’s the most popular Node.js framework.

    Koa

    Development began on Koa in late 2013 by the same guys at Express. It’s referred to as the future of Express. Koa is also described as a much more modern, modular and minimalistic version of the Express framework.

    Hapi.js

    Hapi.js was developed by the team at Walmart Labs (led by Eran Hammer) after they tried Express and discovered that it didn’t work for their requirements. It was originally developed on top of Express, but as time went by, it grew into a full-fledged framework.

    Fun Fact: hapi is short for Http API server.

    Philosophy

    Now that we have some background on the frameworks and how they were created, let’s compare each of them based on important concepts, such as their philosophy, routing, and so on.

    Note: all code examples are in ES6 and make use of version 4 of Express.js, 2.4 of Koa, and 17 for hapi.js.

    Express.js

    Express was built to be a simple, unopinionated web framework. From its GitHub README:

    The Express philosophy is to provide small, robust tooling for HTTP servers, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs.

    Express.js is minimal and doesn’t possess many features out of the box. It doesn’t force things like file structure, ORM or templating engine.

    Koa

    While Express.js is minimal, Koa can boast a much more minimalistic code footprint — around 2k LOC. Its aim is to allow developers be even more expressive. Like Express.js, it can easily be extended by using existing or custom plugins and middleware. It’s more futuristic in its approach, in that it relies heavily on the relatively new JavaScript features like generators and async/await.

    Hapi.js

    Hapi.js focusses more on configuration and provides a lot more features out of the box than Koa and Express.js. Eran Hammer, one of the creators of hapi, described the reason for building the framework properly in his blog post:

    hapi was created around the idea that configuration is better than code, that business logic must be isolated from the transport layer, and that native node constructs like buffers and stream should be supported as first class objects.

    Starting a Server

    Starting a server is one of the basic things we’d need to do in our projects. Let’s examine how it can be done in the different frameworks. We’ll start a server and listen on port 3000 in each example.

    Express.js

    const express = require('express');
    const app = express();
    
    app.listen(3000, () => console.log('App is listening on port 3000!'));
    

    Starting a server in Express.js is as simple as requiring the express package, initializing the express app to the app variable and calling the app.listen() method, which is just a wrapper around the native Node.js http.createServer() method.

    Koa

    Starting a server in Koa is quite similar to Express.js:

    const Koa = require('koa');
    const app = new Koa();
    
    app.listen(3000, () => console.log('App is listening on port 3000!'));
    

    The app.listen() method in Koa is also a wrapper around the http.createServer() method.

    Hapi.js

    Starting a server in hapi.js is quite a departure from what many of us may be used to from Express:

    const Hapi = require('hapi');
    
    const server = Hapi.server({
        host: 'localhost',
        port: 3000
    });
    
    async function start() {
      try {
        await server.start();
      }
      catch (err) {
        console.log(err);
        process.exit(1);
      }
      console.log('Server running at:', server.info.uri);
    };
    
    start();
    

    In the code block above, first we require the hapi package, then instantiate a server with Hapi.server(), which has a single config object argument containing the host and port parameters. Then we start the server with the asynchronous server.start() function.

    Unlike in Express.js and Koa, the server.start() function in hapi is not a wrapper around the native http.createServer() method. It instead implements its own custom logic.

    The above code example is from the hapi.js website, and shows the importance the creators of hapi.js place on configuration and error handling.

    Routing

    Routing is another key aspect of modern web applications. Let’s define a /hello route for a simple Hello World app in each framework to have a feel of how routing works for them.

    Express.js

    app.get('/hello', (req, res) => res.send('Hello World!'));
    

    Creating routes in Express is as simple as calling the app object with the required HTTP method. The syntax is app.METHOD(PATH, HANDLER), where PATH is the path on the server and HANDLER is function which is called when the path is matched.

    Koa

    Koa doesn’t have its own router bundled with it, so we’ll have to use a router middleware to handle routing on Koa apps. Two common routing options are koa-route and koa-router. Here’s an example using koa-route:

    const route = require('koa-route');
    
    app.use(route.get('/hello', ctx => {
        ctx.body = 'Hello World!';
    }));
    

    We can see immediately that Koa needs each route to be defined as a middleware on the app. The ctx is a context object that contains Node’s request and response objects. ctx.body is a method in the response object and can be used to set the response body to either a string, Buffer, Stream, Object or null. The second parameter for the route method can be an async or generator function, so the use of callbacks in reduced.

    Hapi.js

    server.route({
      method: 'GET',
      path:'/hello',
      handler: function (request, h) {
        return 'Hello world!';
      }
    });
    

    The server.route() method in hapi takes a single config object with the following parameters: method, path and handler. You can see the documentation on routing in hapi here.

    The request parameter in the handler function is an object which contains the user’s request details, while the h parameter is described as a response toolkit.

    Middleware

    One of the major concepts Node developers are used to is working with middleware. Middleware functions are functions that sit in between requests and responses. They have access to the request and response objects and can run the next middleware after they’re processed. Let’s take a look at how they’re defined in the different frameworks by implementing a simple function that logs the time a request is made to the server.

    Express.js

    app.use((req, res, next) => {
      console.log(`Time: ${Date.now()}`);
      next();
    })
    

    Registering middleware in Express.js is as simple as binding the middleware to the app object by using the app.use() function. You can read more on middleware in Express.js here.

    Koa

    app.use(async (ctx, next) => {
      console.log(`Time: ${Date.now()}`);
      await next();
    });
    

    Middleware registration in Koa is similar to Express.js. The major differences are that the context object (ctx) is used in place of the request and response objects in Express.js and Koa embraces the modern async/await paradigm for defining the middleware function.

    Hapi.js

    server.ext('onRequest', (request, h) => {
      console.log(`Time: ${Date.now()}`);
      return h.continue;
    });
    

    In hapi.js there are certain extension points in the request lifecyle. The server.ext() method registers an extension function to be called at a certain point in the request life cycle. You can read more about it here. We make use of the onRequest extension point in the example above to register a middleware (or extension) function.

    Usage

    From the comparisons and code examples we’ve seen above, it’s clear that Express and Koa are the most similar, with hapi.js being the framework to deviate from the norm that Node.js devs are used to. Hence hapi.js may not be the best choice when trying to build a quick and easy app, as it will take a bit of time to get used to.

    In my opinion, Express is still a great choice when building small- to medium-sized applications. It can become a bit complicated to manage for very large applications, as it doesn’t possess the modularity hapi.js has built into it, with support for custom plugins and its unique routing method. However, there’s been some speculation in recent times regarding the future of Express.js as TJ announced he’s no longer working on it and the reduced rate at which updates are shipped. Bit it’s pretty stable and willn’t be going away any time soon. It also has a large community of developers building various extensions and plugins for it.

    Like Express.js, Koa is well suited for many simple Node.js projects. It only consists of the bare minimum (it has zero in-built middleware) and encourages developers to add what they need to it by building or making use of available external middleware. It makes use of modern JavaScript generator functions and async/await heavily, which makes it sort of futuristic in its approach. Its middleware cascading pattern is also great, as it makes implementing and understanding the flow of middleware in your applications very easy. Koa probably won’t be a great choice for you if you aren’t yet ready to embrace new shiny things like generator functions, or if you’re not willing to spend some time building out all the middleware you need. The community support for Koa is rapidly growing, as it has a good amount of external middleware already built for it (some by the core Koa team) for common tasks such as routing, logging and so on.

    Hapi.js is the definite choice if you and your team prefer to spend more time configuring than actually coding out features. It was built to be modular and for large applications with large teams. It encourgages the micro-service architecture, as various parts of your app can be built as plugins and registered in your server before starting it up. Hapi.js is backed by large companies such as Auth0 and Lob, so it has a pretty good future ahead of it and won’t be going away anytime soon. It’s also trusted by some big names, as seen on their community page.

    Hapi.js has a whole lot more features out of the box than Koa and Express.js, such as support for authentication, caching, logging, validation and so on, which makes it feel more like a full-fledged framework. You can check out their tutorials page to get a good feel of the features they provide. There aren’t yet very many open-source projects and plugins built on and for hapi.js, so a lot of work might need to be done by developers using it if they plan to extend its core functionality.

    Conclusion

    All three frameworks are great choices when starting up new projects, but ultimately your choice will be based on the project requirements, your team members and the level of flexibility you’re looking for.