How to create a zero dependency HTTP/2 static file server with Node.js
HTTP/2 has been supported by the latest versions of the most popular browsers, including Google Chrome, Firefox, Safari and Microsoft Edge for quite some time now. Websites delivered using HTTP/2 enjoy a wide range of new features including -
- fully multiplexed connections: all requests and responses to a domain are fully multiplexed via a single connection, making best use of available bandwidth,
- header compression: repeated headers are compressed with HPACK compression so that they are not resent with every request and response,
- PUSH: resources can be pre-emptively pushed by the server to the client, speeding up page load times.
Node.js launched support (v8.8.1) for HTTP/2 as part of their core. In this post, we will create a simple HTTP/2 server to serve static files and then demonstrate some cool features like HTTP/2 PUSH.
Step 0: Install Node.js v8.8.1
We will need at least Node.js v8.7.0 to be installed, you can download the latest version here.
Note - HTTP/2 is still considered as an experimental feature (Stability 0) even in the latest version of Node. It is not recommended to run production workloads using this yet, since the API might change.
Step 1: Get an SSL certificate
Even though the HTTP/2 spec does not mandate HTTPS, browsers have decided that they will only support HTTP/2 on a HTTPS connection. If you already have HTTPS enabled, you can skip this step. Otherwise, you can generate a free certificate with letsencrypt or a self-signed certificate for local development.
Step 2: Building a Static File server
Let us start with a simple server which just serves static files. We will be listening to the stream event and responding to it with the corresponding file from the server root using the respondWithFile API.
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const mime = require('mime-types');
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_INTERNAL_SERVER_ERROR
} = http2.constants;
const options = {
key: fs.readFileSync('./selfsigned.key'),
cert: fs.readFileSync('./selfsigned.crt')
}
const server = http2.createSecureServer(options);
const serverRoot = "./public";
function respondToStreamError(err, stream) {
console.log(err);
if (err.code === 'ENOENT') {
stream.respond({ ":status": HTTP_STATUS_NOT_FOUND });
} else {
stream.respond({ ":status": HTTP_STATUS_INTERNAL_SERVER_ERROR });
}
stream.end();
}
server.on('stream', (stream, headers) => {
const reqPath = headers[HTTP2_HEADER_PATH];
const reqMethod = headers[HTTP2_HEADER_METHOD];
const fullPath = path.join(serverRoot, reqPath);
const responseMimeType = mime.lookup(fullPath);
stream.respondWithFile(fullPath, {
'content-type': responseMimeType
}, {
onError: (err) => respondToStreamError(err, stream)
});
});
server.listen(443);
Server Push Example
Now that we have a simple HTTP/2 server running, let’s try to use one of the new features in HTTP/2 - HTTP/2 PUSH. This can lead to significant performance improvements in high latency environments, if done correctly.
We are loading a simple HTML file pointing to style.css which references our font file. The request to the font file is only made after the CSS file is discovered in the HTML, downloaded and then parsed. This is how the waterfall would have usually looked like.

You can initiate a new PUSH with the pushStream API. Since we know that the browser is going to be requesting the font file in the future, we can PUSH the font file as soon as the server receives the request for the HTML file.

const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const mime = require('mime-types');
const {
HTTP2_HEADER_PATH,
HTTP2_HEADER_METHOD,
HTTP_STATUS_NOT_FOUND,
HTTP_STATUS_INTERNAL_SERVER_ERROR
} = http2.constants;
const options = {
key: fs.readFileSync('./selfsigned.key'),
cert: fs.readFileSync('./selfsigned.crt')
}
const server = http2.createSecureServer(options);
const serverRoot = "./public";
function respondToStreamError(err, stream) {
console.log(err);
if (err.code === 'ENOENT') {
stream.respond({ ":status": HTTP_STATUS_NOT_FOUND });
} else {
stream.respond({ ":status": HTTP_STATUS_INTERNAL_SERVER_ERROR });
}
stream.end();
}
server.on('stream', (stream, headers) => {
const reqPath = headers[HTTP2_HEADER_PATH];
const reqMethod = headers[HTTP2_HEADER_METHOD];
const fullPath = path.join(serverRoot, reqPath);
const responseMimeType = mime.lookup(fullPath);
if (fullPath.endsWith(".html")) {
stream.respondWithFile(fullPath, {
"content-type": "text/html"
}, {
onError: (err) => {
respondToStreamError(err, stream);
}
});
stream.pushStream({ ":path": "/font.woff" }, { parent: stream.id }, (pushStream) => {
pushStream.respondWithFile(path.join(serverRoot, "/font.woff"), {
'content-type': "text/css"
}, {
onError: (err) => {
respondToStreamError(err, pushStream);
}
});
});
} else {
stream.respondWithFile(fullPath, {
'content-type': responseMimeType
}, {
onError: (err) => respondToStreamError(err, stream)
});
}
});
server.listen(443);
You can check out the full API for HTTP/2 in node.js here.
Using the compatibility API
Node.js also provides a compatibility layer to make it easier to get started with HTTP/2 on your existing web application.
You simply need to replace your existing https module with the new http2 module and things should work out of the box. Make sure you pass in the allowHTTP1 flag when creating the server. This would downgrade browsers which don’t support HTTP/2 back to a HTTP/1.1 connection.
const http2 = require('http2');
const fs = require('fs');
const options = {
key: fs.readFileSync('./selfsigned.key'),
cert: fs.readFileSync('./selfsigned.crt'),
allowHTTP1: true
}
const server = http2.createSecureServer(options, (req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end('ok');
});
server.listen(443);
It would still take time before full-blown frameworks like Express catch up to ensure that their API doesn’t break when used with HTTP/2, but with HTTP/2 server and client adoption catching up rapidly, we should get there soon!