Skip to main content

Express HTTP/2 Support

Introduction

HTTP/2 is a major revision of the HTTP network protocol used by the World Wide Web. It was derived from the earlier experimental SPDY protocol, originally developed by Google, and focuses on improving website performance, particularly in terms of page load speed. As modern web applications grow in complexity, implementing HTTP/2 can significantly enhance your Express application's performance.

In this guide, we'll explore how to add HTTP/2 support to your Express applications, understand its benefits, and learn implementation best practices. HTTP/2 offers several advantages over HTTP/1.1, including multiplexing, header compression, server push, and binary protocols.

Understanding HTTP/2 Benefits

Before diving into implementation, let's understand why HTTP/2 matters:

  1. Multiplexing: Allows multiple requests and responses to be sent concurrently over a single connection
  2. Header Compression: Reduces overhead by compressing HTTP headers
  3. Server Push: Enables servers to send resources to the client before they are requested
  4. Binary Protocol: More efficient to parse and less error-prone than the text-based HTTP/1.1

For Express applications, these features translate to faster page loads, reduced latency, and better utilization of available bandwidth.

Setting Up HTTP/2 in Express

Unlike HTTP/1.1, HTTP/2 in practice requires TLS encryption (HTTPS). While the standard doesn't strictly require it, most browsers only implement HTTP/2 over TLS. Let's start by setting up an HTTP/2 server with Express:

Prerequisites

  • Node.js 8.4.0 or higher
  • Basic understanding of Express.js
  • SSL/TLS certificate (self-signed for development or from a Certificate Authority for production)

Step 1: Install Required Packages

bash
npm install express spdy --save

We're using the spdy package which provides HTTP/2 implementation for Node.js. While Node.js has a native http2 module, the spdy package is often used with Express due to its compatibility and ease of integration.

Step 2: Create SSL Certificates (for Development)

For development purposes, you can create self-signed certificates:

bash
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
-keyout private-key.pem -out certificate.pem

For production, use certificates from trusted Certificate Authorities like Let's Encrypt.

Step 3: Create Your HTTP/2 Express Server

Here's a basic example of setting up an Express server with HTTP/2:

javascript
const express = require('express');
const spdy = require('spdy');
const fs = require('fs');
const path = require('path');

// Initialize Express
const app = express();

// Define routes
app.get('/', (req, res) => {
res.send('Hello HTTP/2 world!');
});

// Options for HTTP/2 server
const options = {
key: fs.readFileSync(path.join(__dirname, 'private-key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'certificate.pem'))
};

// Create HTTP/2 server
spdy.createServer(options, app)
.listen(3000, (err) => {
if (err) {
console.error('An error occurred:', err);
return;
}
console.log('HTTP/2 server listening on port 3000');
});

Step 4: Test Your HTTP/2 Implementation

You can verify that your server is properly serving HTTP/2 using various tools:

  1. Browser Developer Tools: Open Chrome DevTools, go to the Network tab, and add the "Protocol" column to see "h2" for HTTP/2 connections.

  2. Using curl command:

bash
curl -v --http2 https://localhost:3000/
  1. Using online tools like KeyCDN HTTP/2 Test

Advanced Features: Server Push

One of HTTP/2's powerful features is Server Push, which allows you to send resources to the client before they are explicitly requested. Here's how to implement it in Express:

javascript
app.get('/', (req, res) => {
// Push CSS file
const stream = res.push('/styles/main.css', {
request: {
accept: '*/\*'
},
response: {
'content-type': 'text/css'
}
});

stream.on('error', err => {
console.error('Server push error:', err);
});

// Write CSS content to the push stream
stream.end('body { background-color: #f0f0f0; }');

// Send the main HTML response
res.writeHead(200, { 'content-type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="/styles/main.css">
</head>
<body>
<h1>HTTP/2 Server Push Example</h1>
</body>
</html>
`);
});

This example pushes a CSS file to the client along with the HTML page, reducing the need for the client to make a separate request for the stylesheet.

Real-World Implementation Example

Let's look at a more comprehensive example of an Express application optimized for HTTP/2:

javascript
const express = require('express');
const spdy = require('spdy');
const fs = require('fs');
const path = require('path');
const compression = require('compression');

// Initialize Express
const app = express();

// Add middleware
app.use(compression()); // Compress responses
app.use(express.static(path.join(__dirname, 'public')));

// API routes
app.get('/api/data', (req, res) => {
res.json({ message: 'This is coming over HTTP/2!' });
});

// Server Push for homepage
app.get('/', (req, res) => {
// Push critical assets
const assets = [
'/css/main.css',
'/js/app.js',
'/images/logo.png'
];

assets.forEach(asset => {
const stream = res.push(asset, {});
const filePath = path.join(__dirname, 'public', asset);

stream.on('error', err => {
console.error(`Error pushing ${asset}:`, err);
});

if (fs.existsSync(filePath)) {
fs.createReadStream(filePath).pipe(stream);
} else {
stream.end();
}
});

// Send the main HTML file
const indexPath = path.join(__dirname, 'public', 'index.html');
fs.createReadStream(indexPath).pipe(res);
});

// HTTPS Options
const options = {
key: fs.readFileSync(path.join(__dirname, 'certificates/private-key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'certificates/certificate.pem'))
};

// Create HTTP/2 server
spdy.createServer(options, app)
.listen(3000, (err) => {
if (err) {
console.error('Error starting server:', err);
return;
}
console.log('HTTP/2 server listening on port 3000');
});

This implementation includes:

  • Compression middleware for smaller response sizes
  • Static file serving
  • API endpoint
  • Server Push for critical assets on the homepage
  • Proper error handling

Best Practices for HTTP/2 with Express

  1. Consolidate your code: With HTTP/1.1, a common optimization was to split assets into multiple files to allow parallel downloads. With HTTP/2's multiplexing, fewer larger files can be more efficient.

  2. Reconsider domain sharding: This technique (splitting resources across domains) is unnecessary with HTTP/2 and can be counterproductive.

  3. Prioritize server push wisely: Only push truly critical resources that you're confident the client will need.

  4. Keep using compression: Even though HTTP/2 is more efficient, compression still provides significant benefits.

  5. Optimize your SSL/TLS setup: HTTP/2 performance depends heavily on efficient TLS implementation.

Common Challenges and Solutions

Challenge 1: Node's Native HTTP/2 Module vs SPDY

Node.js provides a native http2 module, but it doesn't integrate seamlessly with Express. Here's how you can use the native module if preferred:

javascript
const express = require('express');
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const { Http2ServerRequest, Http2ServerResponse } = http2;

const app = express();

// Express route
app.get('/', (req, res) => {
res.send('Hello HTTP/2 world!');
});

// Create HTTP/2 server
const server = http2.createSecureServer({
key: fs.readFileSync(path.join(__dirname, 'private-key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'certificate.pem'))
});

// Handle requests with Express
server.on('request', (req, res) => {
app(req, res);
});

server.listen(3000, () => {
console.log('HTTP/2 server listening on port 3000');
});

Challenge 2: Browser Support

While most modern browsers support HTTP/2, you might need to provide fallbacks for older browsers:

javascript
const express = require('express');
const spdy = require('spdy');
const http = require('http');
const fs = require('fs');
const path = require('path');

const app = express();

app.get('/', (req, res) => {
res.send('Hello world!');
});

// HTTP/2 server
const http2Server = spdy.createServer({
key: fs.readFileSync(path.join(__dirname, 'private-key.pem')),
cert: fs.readFileSync(path.join(__dirname, 'certificate.pem'))
}, app).listen(3000, () => {
console.log('HTTP/2 server listening on port 3000');
});

// HTTP/1.1 fallback server
const httpServer = http.createServer(app).listen(8080, () => {
console.log('HTTP/1.1 fallback server listening on port 8080');
});

Performance Monitoring

To ensure your HTTP/2 implementation is delivering the expected benefits, consider using these monitoring tools:

  1. Browser Developer Tools: Chrome and Firefox developer tools can show HTTP/2 protocol details.

  2. Lighthouse: Google's automated tool for improving web page quality.

  3. WebPageTest: Provides detailed performance analysis including HTTP/2 vs HTTP/1.1 comparison.

  4. h2load: A command-line benchmarking tool specific to HTTP/2:

bash
h2load -n 100 -c 10 https://example.com/

Summary

HTTP/2 offers significant performance improvements for Express applications, particularly for complex web apps with many resources. By implementing HTTP/2, you can benefit from multiplexing, header compression, server push, and binary protocols to deliver a faster experience to your users.

Key takeaways from this guide:

  1. HTTP/2 requires HTTPS in practice (though not by specification)
  2. The spdy package provides the easiest integration with Express
  3. Server Push can be leveraged to proactively send critical resources
  4. Optimization strategies differ from HTTP/1.1, with less need for techniques like file splitting and domain sharding
  5. Proper monitoring is essential to ensure performance benefits

Additional Resources

Exercises

  1. Set up a basic Express server with HTTP/2 support and verify it's working using browser developer tools.
  2. Implement server push for CSS and JavaScript files on a simple webpage.
  3. Compare the load times of a complex web page served over HTTP/1.1 versus HTTP/2.
  4. Create a fallback system that serves your application over both HTTP/2 and HTTP/1.1 depending on client support.
  5. Analyze your current Express application and identify opportunities to optimize for HTTP/2.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)