MoroJS now includes full native HTTP/2 support with advanced features including server push, stream prioritization, and multiplexing.
- ✅ Native HTTP/2 Server - Built on Node.js
http2module - ✅ Server Push - Proactively push resources to clients
- ✅ Stream Prioritization - Control resource loading order
- ✅ Multiplexing - Multiple requests over single connection
- ✅ Header Compression - HPACK compression built-in
- ✅ HTTP/1.1 Fallback - Automatic fallback support
- ✅ Auto-detect Push - Automatically detect and push CSS/JS from HTML
import { Moro } from '@morojs/moro';
import * as fs from 'fs';
const app = new Moro({
http2: true,
https: {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
},
});
app.get('/', (req, res) => {
res.json({
message: 'Hello HTTP/2!',
version: req.httpVersion, // '2.0'
});
});
app.listen(3000);const app = new Moro({
http2: {
allowHTTP1: true, // Support HTTP/1.1 fallback
settings: {
maxConcurrentStreams: 100,
initialWindowSize: 65535,
maxFrameSize: 16384,
},
},
https: {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
},
});Use the http2.push middleware with auto-detection:
import { middleware } from '@morojs/moro';
app.use(
middleware.http2.push({
autoDetect: true, // Auto-detect CSS/JS from HTML
resources: [
{
path: '/styles/main.css',
as: 'style',
type: 'text/css',
priority: 200, // High priority
},
{
path: '/scripts/app.js',
as: 'script',
type: 'application/javascript',
priority: 150,
},
],
condition: req => req.path === '/' || req.path.endsWith('.html'),
})
);Push resources manually within route handlers:
app.get('/', (req, res) => {
// Check if push is available
if (res.push) {
// Push CSS with high priority
const cssStream = res.push('/styles/main.css', {
headers: { 'content-type': 'text/css' },
priority: 200,
});
if (cssStream) {
cssStream.end('body { font-family: Arial; }');
}
// Push JavaScript
res.push('/scripts/app.js', {
headers: { 'content-type': 'application/javascript' },
priority: 150,
});
}
res.send('<html>...</html>');
});Control the priority of your responses:
// Critical API endpoint - highest priority
app.get('/api/critical', (req, res) => {
if (res.setPriority) {
res.setPriority({
weight: 256, // 1-256, higher = more important
exclusive: true, // Exclusively process this stream
});
}
res.json({ critical: 'data' });
});
// Background data - low priority
app.get('/api/background', (req, res) => {
if (res.setPriority) {
res.setPriority({ weight: 1 });
}
res.json({ background: 'data' });
});Priority weight ranges from 1-256:
- 256 - Critical resources (HTML, critical API data)
- 200 - High priority (CSS, fonts)
- 150 - Medium priority (JavaScript)
- 100 - Normal priority (images)
- 50 - Low priority (analytics, tracking)
- 1 - Lowest priority (background tasks)
app.get('/', (req, res) => {
const httpVersion = req.httpVersion; // '2.0' or '1.1'
if (httpVersion === '2.0') {
// Use HTTP/2 features
res.push('/assets/critical.css');
}
res.json({ version: httpVersion });
});HTTP/2 requires SSL/TLS. For development, generate self-signed certificates:
# Generate self-signed certificate
openssl req -x509 -newkey rsa:2048 -nodes -sha256 \
-subj '/CN=localhost' \
-keyout localhost-key.pem \
-out localhost-cert.pemFor production, use certificates from a trusted CA like Let's Encrypt.
Only push resources that are:
- Critical for initial render
- Small in size (< 100KB)
- Used by most users
app.use(
middleware.http2.push({
autoDetect: true, // Let MoroJS detect from HTML
resources: [
// Only push critical resources
{ path: '/critical.css', as: 'style', type: 'text/css', priority: 200 },
],
})
);// Critical path
res.setPriority({ weight: 256, exclusive: true });
// Important but not critical
res.setPriority({ weight: 200 });
// Background/analytics
res.setPriority({ weight: 1 });const app = new Moro({
http2: {
allowHTTP1: true, // Clients without HTTP/2 support
},
https: {/* ... */},
});app.get('/', (req, res) => {
const pushStream = res.push('/asset.css');
if (pushStream) {
pushStream.on('error', err => {
console.log('Push failed:', err.message);
});
}
});const app = new Moro({
http2: {
settings: {
maxConcurrentStreams: 100, // Default: 100
initialWindowSize: 65535, // 64KB
maxFrameSize: 16384, // 16KB
},
},
});const app = new Moro({
http2: {
maxSessionMemory: 10, // MB per session
settings: {
headerTableSize: 4096, // HPACK table size
maxHeaderListSize: 8192, // Max header size
},
},
});All MoroJS middleware works with HTTP/2:
import { middleware } from '@morojs/moro';
app.use(middleware.cors());
app.use(middleware.helmet());
app.use(middleware.compression()); // Works alongside HTTP/2 compression
app.use(middleware.requestLogger());# Test HTTP/2 endpoint
curl -k --http2 https://localhost:3000/
# Check protocol version
curl -k --http2 -I https://localhost:3000/ | grep HTTPconst http2 = require('http2');
const client = http2.connect('https://localhost:3000', {
rejectUnauthorized: false, // For self-signed certs
});
const req = client.request({ ':path': '/' });
req.on('response', headers => {
console.log('Status:', headers[':status']);
});
req.on('data', chunk => {
console.log('Data:', chunk.toString());
});
req.end();Open Chrome DevTools → Network tab → Protocol column shows h2 for HTTP/2.
const app = new Moro();
app.get('/', (req, res) => {
res.send('<html>...</html>');
});
app.listen(3000);const app = new Moro({
http2: true,
https: {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
},
});
app.use(middleware.http2.push({ autoDetect: true }));
app.get('/', (req, res) => {
// Optionally use HTTP/2 features
if (res.push) {
res.push('/critical.css');
}
res.send('<html>...</html>');
});
app.listen(3000);-
Check if client supports HTTP/2:
if (req.httpVersion === '2.0' && res.push) { // Push is available }
-
Ensure SSL is configured:
// HTTP/2 requires HTTPS https: { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem'), }
-
Check whether the client allows push. Server push is enabled by the client's
SETTINGS_ENABLE_PUSH, not the server's: RFC 9113 forbids a server from advertising it (nghttp2-based clients drop the connection if one does), sosettings.enablePushis accepted but never sent. Moro reads the client's choice per stream (stream.pushAllowed) andres.push()returnsnullwhen it is off. Chrome and Firefox have disabled push, so expectnullfrom browsers and rely on<link rel="preload">there.
- Verify certificates are valid
- Check
maxConcurrentStreamssetting - Monitor memory usage with
maxSessionMemory
- Don't over-push resources
- Set appropriate priorities
- Monitor stream counts
- Adjust
initialWindowSizeif needed
See /examples/http2-server.ts for a complete working example with:
- Server push configuration
- Stream prioritization
- Auto-detection of resources
- Manual push examples
- Priority testing endpoints
Push a resource to the client.
res.push(path: string, options?: {
headers?: Record<string, string>;
priority?: number; // 1-256
});Set stream priority for current response.
res.setPriority(options?: {
parent?: number;
weight?: number; // 1-256
exclusive?: boolean;
});Configure server push behavior.
middleware.http2.push({
autoDetect?: boolean;
resources?: Array<{
path: string;
as: string;
type?: string;
priority?: number;
}>;
condition?: (req) => boolean;
});