Hapi vs. Express in 2019: Node.js framework comparison

| 7 min. (1354 words)

This article was last updated January 2019.

Here at Raygun, before we implement any new tool, we always run performance tests and like to share the results. This time we’re comparing Hapi vs. Express to help you make a more informed choice on Node.js frameworks.

Node has become a staple of the software development industry. The increased popularity of JavaScript over the past several years has propelled Node forward in terms of installed base. JavaScript and Node offer, perhaps for the first time, the opportunity to develop entire n-tier applications using a single language.

Node is fast, and JavaScript is everywhere. It’s a perfect match.

As it is with all web platforms, Node provides the essentials for an application: request and response objects, methods to manipulate HTTP requests, and more (but not much more.) “Pure” Node apps are wicked fast, but they lack in the supporting cast of middleware, routing, and plugins that reduce the amount of code needed for a modern web application. This is where web frameworks shine.

This is the story of the battle between two popular frameworks: Hapi and Express.

Hapi vs. Express: A comparison

Both frameworks have more in common than they have differences. However, there are some key differences you should consider if you’re choosing between them for a project.

Hapi (short for (Http)API, pronounced “happy”) is a newer framework that abstracts the existing Node API. Express is older and more established. Express code looks and feels more like native Node.

Hapi has more in the core

There are some cases where Express needs middleware to perform a task that Hapi handles internally. Forms processing is a good example.

Hapi parses forms data and exposes it on the request object. Express, by contrast, needs the body-parser middleware to offer the same functionality.

Express is closer to Node

Express is somewhat less opinionated than Hapi, in the sense that it is less abstracted from Node. Both frameworks are extensible and adaptable. However, Express “feels” more like a native Node application. Hapi provides more abstraction from Node. Long-time Node developers may prefer Express for its familiarity, or they may appreciate the abstractions provided by Hapi.

Hapi uses plugins, and Express uses middleware

Express uses middleware to provide developers access to the request/response pipeline. Developers have access to Node’s req and res request/response objects. An Express application “chains” middleware together to act on requests and responses. Each middleware component has a single, well-defined job to do, keeping concerns isolated within each component.

Hapi, by contrast, uses plugins to extend its capabilities. Plugins are configured at runtime through code. There are a wide variety of Hapi plugins, for capabilities including routing, authentication, logging, and more. There is usually a Hapi plugin for every Express middleware component, making Express and Hapi more or less equal regarding capabilities.

Express Hello World

Here is a bare-bones Express application:

var express = require('express');  
var app = express();

app.get('/', function (req, res) {  
  res.send('Hello World!');  
  });

  app.listen(3000, function () {  
    console.log('Example app listening on port 3000!');  
    });
    

It’s short and sweet and gets the job done. The app.get function defines a handler for the / request, which returns the text “Hello World!”. The handler takes req (request) and res (response) parameters. The last line starts the server.

Here’s what the same sample looks like with a little middleware:

var express = require('express');
var cookieParser = require('cookie-parser');

var app = express();
app.use(cookieParser());

app.get('/', function (req, res) {
  console.log("Cookies: ", req.cookies);
  res.cookie("greeted", "true").send('Hello World!');
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!');
});

This example includes the cookie-parser Express middleware, which is used to read and write cookies from requests. The middleware extends the res object with methods and properties related to cookies.

Hapi Hello World

'use strict';

const Hapi = require('hapi');

// Create a server with a host and port  
const server = Hapi.server({
  host: 'localhost',
  port: 8000
});

// Add the route  
server.route({
  method: 'GET',
  path: '/hello',
  handler: function (request, h) {

    return 'Hello World!';
  }
});

// Start the server  
async function start () {

  try {
    await server.start();
  }
  catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
};

start();

Hapi is a little more verbose than Express. Hapi applications center around the server object. The behavior of the application is configured by setting properties of that object. In contrast, the Express app has app.get, and Hapi exposes a server.route collection.

Now, let’s take a look at how cookies are handled:

'use strict';

const Hapi = require('hapi');

// Create a server with a host and port  
const server = Hapi.server({
  host: 'localhost',
  port: 8000
});

server.state('myCookie', {
  ttl: null,
  isSecure: true,
  isHttpOnly: true,
  encoding: 'base64json',
  clearInvalid: false, // remove invalid cookies
  strictHeader: true // don't allow violations of RFC 6265
});

// Add the route  
server.route({
  method: 'GET',
  path: '/hello',
  handler: function (request, h) {
    const cookie = request.state.myCookie;
    console.log(cookie);
    return response('Hello World!').state('myCookie', { greeted: true });
  }
});

// Start the server  
async function start () {

  try {
    await server.start();
  }
  catch (err) {
    console.log(err);
    process.exit(1);
  }

  console.log('Server running at:', server.info.uri);
};

start();

Hapi includes cookie-handling in its core—we don’t even need a plugin to set and read cookies. The cookie is configured using the server.state property, then read and written in the GET handler for the / route.

Express and Hapi are generally equally capable. Their differences are mostly philosophical. Some applications may benefit from the abstractions provided by Hapi. Other apps may be better off with the “close to the metal” way of Express.

Although both equally capable, Hapi and Express are not equally performant. Let’s take a look at how they benchmark compared to each other, and to a pure Node app.

The performance test: How did they do?

The performance test for Hapi vs. Express was performed on a D-series Azure VM running Ubuntu. Typically, we’d include a test on a local system. Unfortunately, socket errors in the Windows Subsystem for Linux invalidated that test.

As usual, the test used Apache Bench to measure request performance. Each framework benchmark was repeated five times, and the final results below were the average of the five tests.

I tested the following versions:

NodeJS v9.11.1

ExpressJS v4.16.3

HapiJS v17.3.1

Results

Here are the results of the test:

Azure D-Series Ubuntu VM

Framework req/s
Node 7770.042
Express 4570.692
Hapi 3992.902

hapi vs. express results

What these results mean for you

This test was consistent with past results. Express continues to maintain a performance edge over Hapi. The difference is not huge, but it is measurable. Applications with significant performance requirements should consider the advantage Express has over Hapi. If performance is less of a concern, you may prefer the extras provided by Hapi “out of the box.” Outside of performance, most of the effects from the differences are on readability and maintainability of code, depending on the application.

How to replicate the Hapi vs. Express test

If you’d like to try these benchmarks for yourself, clone the Github repository then execute the following commands:

bash
./install.sh
./run.sh

The first script installs tooling and downloads dependencies. When the script gets to the Sails bootstrapping step, create an Empty application. Run the run.sh script to execute the benchmarks. Note that you may have to edit the script and increase the wait time between tests, to get all the tests to run properly. The results output to results.txt.

Happy benchmarking!

Koa vs. Express

React Native and TypeScript: Developing cross-platform applications

Mocha vs. Jasmine in 2019: A performance test