Hapi vs. Express in 2019: Node.js framework comparison
Posted Jun 5, 2018 | 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.
Raygun lets you detect and diagnose errors and performance issues in your codebase with ease
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.
Raygun lets you detect and diagnose errors and performance issues in your codebase with ease
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 |
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!
Related reading:
React Native and TypeScript: Developing cross-platform applications