How to create an HTTP/2 server in Node.js

HTTP/2 is here and we can already use it in our applications. It provides lower latency through its features. In this post, I'll create an HTTP/2 server using the http2 module of Node.js.

HTTP/2 is meant to be the replacement of the HTTP/1.x protocol without changing the currently used methods, headers and status codes.

The main goal of HTTP/2 was the reduce latency, increase speed and get rid of the web optimization hacks developers need to do to load applications faster.

HTTP/2 is based on SPDY, which was developed by Google and then it served as a test branch for the evolving HTTP/2 protocol. Later, the SPDY project died and the new HTTP/2 protocol was born.

Today, all major browsers support HTTP/2, and even Internet Explorer has partial support, so this thing can’t be that bad.

The shiny features of HTTP/2

HTTP/2, while keeping the good things of HTTP/1.x, has some new features.

First, HTTP/2 is binary instead of being textual. Binary protocols are more efficient to parse, more compact and less error prone.

Multiplexing allows the browser to make multiple requests in one connection. With HTTP/1, browsers make several connections to receive all the necessary assets and because each connection will start receiving data in the response, it can easily cause buffer issues.

HTTP/2 also comes with server push. This means that the server can push the necessary assets to the browser, so when the browser needs a CSS file, for example, it doesn’t need to make the request because the file is already there. This feature can also save time when the application loads.

HTTP/2 compresses the headers. Required assets can have a lot of headers (e.g. cookies), and they might contain duplications of the same piece of information. Compression on headers makes it faster to get them out of the client.

Although HTTP/2 does not require encryption, browsers don’t allow the use of the protocol without it, so they enforce the HTTP/2 over TLS. This practically means that these websites can only be access with HTTPS.

With all of these benefits, web developers won’t need to use tricks like modularization and file concatenation. For example, if we change the margin of a paragraph, this tiny adjustment will create a whole new big concatenated file invalidating cache.

Because HTTP/2 makes parallel requests, big files become unnecessary, what’s more, they are even a disadvantage because of the cache invalidation. Creating smaller asset files help the application load faster.

Create an HTTP/2 server

Node.js has a built-in, low level http2 module and we’ll have a look how to create an HTTP/2 server using this module below.

But first, we’ll need to create a self-signed SSL/TSL certificate. It’s fine to do so for development purposes. In production, one has to get a certificate from a certificate authority.

Generate certificate and key

We’ll use the openssl CLI to create a self-signed certificate and a private key. By typing openssl version in the terminal, you can see if the toolkit is installed on your machine. If it’s not installed, please refer to the OpenSSL website for information on how to install it.

The process of creating a certificate consists of three steps:

  1. Generate a private (and public) key
  2. Create a CSR (Certificate Signing Request)
  3. Get the certificate

Let’s quickly go over each step.

1. Generate a private key

Generating a private key can happen in various ways, depending on the choice of encrypting algorithm, key size or passphrase. In this example, I’ll use rsa as the algorithm and 2048 as the key size, and I won’t have password encryption on the private key.

Navigate to the project folder and enter the following command to the terminal:

openssl genrsa -out server.key 2048

genrsa stands for generating an RSA key and the 2048 refers to the key size. The name of the file containing the key is server.key.

If everything goes well (and why not), server.key should look like this:

-----BEGIN RSA PRIVATE KEY-----
<!-- lots of characters here -->
-----END RSA PRIVATE KEY-----

This file contains both the private and the public key. This file should never be shared, because private keys hold sensitive information and they can easily be generated with just a line of command as it can be seen.

2. Create a CSR

The second step is to create a Certificate Signing Request:

openssl req -new -key server.key -out server.csr

The req stands for request: We request a new CSR named server.csr (the -out argument, this will be the end result), and the private key in server.key (-key) will be used for this request. With this, it’s ensured that we are requesting the CSR and not someone else.

When we press Enter, we need to answer a bunch of questions about our location, company etc. When a real certificate is needed in production, these questions should be appropriately answered, but for now, you can type anything, because the certificate will only serve development purposes.

The server.csr file should look like this:

-----BEGIN CERTIFICATE REQUEST-----
<!-- lots of characters here again -->
-----END CERTIFICATE REQUEST-----

The CSR contains the information you provide as answers to the questions and the public key, which is extracted from server.key.

Now that we have a CSR, we can get a certificate.

3. Get a certificate

In production, when a real certificate is needed, you will need to send the .csr s file to a Certificate Authority, and they will issue you the certificate, which will replace the one we’ll generate now.

For now, we’ll create a self-signed certificate with the following command:

openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

The x509 command is certificate utility. Here we request (req) a certificate named server.crt, which is encrypted with SHA-256, is valid for 365 days, and is based on the server.csr CSR and signed by the private key in server.key.

We have now everything to create a HTTP/2 server.

Create a Node.js HTTP/2 server

The built-in http2 module provides an interface of the HTTP/2 protocol.

Let’s see one way we can create an HTTP/2 server using Node. Given that the key and the certificate are located in the same folder as server.js, we can have the following code:

// server.js

const http2 = require('http2')
const fs = require('fs')

const PORT = 3000

const options = {
  key: fs.readFileSync(`${ __dirname }/server.key`),
  cert: fs.readFileSync(`${ __dirname }/server.crt`)
}

const server = http2.createSecureServer(options, (req, res) => {
  res.end('<h1>Hello World!</h1>')
})

server.listen(PORT, (err) => {
  if (err) {
    console.error(err)
    return process.exit(1)
  }

  console.log(`Server is running on port ${ PORT }`)
})

We import both the http2 and the fs modules, and define the PORT the server will listen to.

The server itself is created using the createSecureServer method, because the traffic goes over TLS (which eventually means HTTPS). This is how modern browsers support HTTP/2.

The first argument of the method is options, where we can declare the key and certificate created in the last step.

The second argument is a callback function with the compulsory Hello World!. I wouldn’t feel good if I didn’t include it.

After we defined the server, we can start it using the listen method.

All we have to do now is to start the server in the terminal by typing node server.js, and the Server is running on port 3000 should appear there.

Now open a browser window and navigate to https://localhost:3000. Both Chrome and Firefox will give us a warning that the connection is not secure.

This is because they are worried about our safety and they don’t trust self-signed certificates for HTTPS either. In both browsers, you need to go to Advanced and go to localhost (Chrome) or add exception for the certificate (Firefox).

And… The Hello World! should immediately be visible. If you go to the developer tools by pressing Ctrl + Shift + I, you should see h2 or HTTP/2.0 in the Protocol field in the Network tab. You might need to add this field by right clicking on the dev tools header.

Does it work with API frameworks?

This has been really good and impressive so far (at least, I’m impressed), but be honest, the real world requires a bit more than some Hello World.

We create more complex applications in life and use frameworks that boost up Node’s low level server API.

Express

Let’s see how we go with Express, the most widely used framework.

Try to serve the index.html file, which is in the same folder as server.js (and also displays a Hello World! message in my case):

// server.js

const express = require('express')
const http2 = require('http2')
const fs = require('fs')

const PORT = 3000
const app = express()

app.get('/', (req, res) => {
  res.status(200).sendFile(`${ __dirname }/index.html`)
})

const options = {
  key: fs.readFileSync(`${ __dirname }/server.key`),
  cert: fs.readFileSync(`${ __dirname }/server.crt`)
}

http2.createSecureServer(options, app)
  .listen(PORT, (err) => {
    if (err) {
      console.error(err)
      return process.exit(1)
    }

    console.log(`Server is running on port ${ PORT }`)
  })

In this case, I replace the callback of the createSecureServer method with the Express app and chain the listen method to it.

node server.js starts the server but when we navigate to port 3000, we’ll get an error message. The above code doesn’t work; at least I couldn’t make it work.

Apparently, at the time of writing this, there’s a mismatch between the http2 module and Express. According to the news, this will be fixed in version 5, but it’s too bad for now.

So what can we do if we want to use Express?

node-spdy

Although the SPDY project is deprecated, node-spdy is very well alive and popular.

All we have to do is to install node-spdy with npm i spdy, and replace the native http2 with it:

// server.js

const express = require('express')
const spdy = require('spdy') // replace http2 with spdy

// ... nothing changes here

spdy.createSecureServer(options, app)
  .listen(PORT, (err) => {
    // ... no change here
  })

This code works as expected, but unfortunately, we can’t use the http2 module yet.

Other frameworks

If you don’t insist on Express, you can make this work using other API frameworks.

The http2 module works well with Koa and Hapi. Please refer to the documentation on how to set up a simple server with them.

Conclusion

HTTP/2 proves to be much faster than the currently most widely used HTTP/1.x protocol. Although it has its limitations, this will (or already is) the way to go.

The http2 module in Node.js provides a low level API and, unfortunately, it doesn’t work with Express at the time of writing this post. It works well with Koa and Hapi, though.

If one is happy using node-spdy, it integrates well into Express, and with a small modification in the code, one can have a fast and secure HTTP/2 server for their application.

Thanks for reading and see you next time.