Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions frameworks/express/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends python3 make g++ && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json .
RUN npm install --omit=dev
COPY server.js .
ENV NODE_ENV=production
EXPOSE 8080
CMD ["node", "server.js"]
20 changes: 20 additions & 0 deletions frameworks/express/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Express

[Express](https://github.com/expressjs/express) (~69k ⭐) — the most widely used backend framework in the JavaScript ecosystem. Fast, unopinionated, minimalist web framework for Node.js.

## Setup

- **Express 5.x** with Node.js 22
- **Cluster mode** — one worker per CPU core
- **better-sqlite3** for `/db` endpoint (mmap, read-only)
- **HTTP/2** on port 8443 (native `http2` module, raw handler for performance)
- **Pre-computed gzip** for `/compression`

## Why Express?

HttpArena already has bare `node`, `fastify`, `ultimate-express`, `hono`, and `bun` — but not vanilla Express itself. Express is the framework that started it all for Node.js web development.

The key comparisons:
- **Express vs Fastify**: The classic Node.js framework showdown — Express's flexibility vs Fastify's schema-based speed
- **Express vs ultimate-express**: How does the original compare to its high-performance reimplementation?
- **Express vs bare node**: How much overhead does the Express middleware layer actually add?
21 changes: 21 additions & 0 deletions frameworks/express/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"display_name": "Express",
"language": "JS",
"type": "framework",
"engine": "V8",
"description": "Fast, unopinionated, minimalist web framework for Node.js. The most widely used backend framework in the JavaScript ecosystem.",
"repo": "https://github.com/expressjs/express",
"enabled": true,
"tests": [
"baseline",
"pipelined",
"limited-conn",
"json",
"upload",
"compression",
"mixed",
"noisy",
"baseline-h2",
"static-h2"
]
}
9 changes: 9 additions & 0 deletions frameworks/express/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "httparena-express",
"version": "1.0.0",
"private": true,
"dependencies": {
"express": "^5.1.0",
"better-sqlite3": "^11.0.0"
}
}
233 changes: 233 additions & 0 deletions frameworks/express/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
const cluster = require('cluster');
const os = require('os');
const fs = require('fs');
const http = require('http');
const http2 = require('http2');
const zlib = require('zlib');

const SERVER_NAME = 'express';

// --- Shared data (loaded per-worker) ---
let datasetItems;
let largeJsonBuf;
let dbStmt;
const staticFiles = {};
const MIME_TYPES = {
'.css': 'text/css', '.js': 'application/javascript', '.html': 'text/html',
'.woff2': 'font/woff2', '.svg': 'image/svg+xml', '.webp': 'image/webp', '.json': 'application/json'
};

function loadStaticFiles() {
const dir = '/data/static';
try {
for (const name of fs.readdirSync(dir)) {
const buf = fs.readFileSync(dir + '/' + name);
const ext = name.slice(name.lastIndexOf('.'));
staticFiles[name] = { buf, ct: MIME_TYPES[ext] || 'application/octet-stream' };
}
} catch (e) {}
}

function loadDataset() {
const path = process.env.DATASET_PATH || '/data/dataset.json';
try {
datasetItems = JSON.parse(fs.readFileSync(path, 'utf8'));
} catch (e) {}
}

function loadLargeDataset() {
try {
const raw = JSON.parse(fs.readFileSync('/data/dataset-large.json', 'utf8'));
const items = raw.map(d => ({
id: d.id, name: d.name, category: d.category,
price: d.price, quantity: d.quantity, active: d.active,
tags: d.tags, rating: d.rating,
total: Math.round(d.price * d.quantity * 100) / 100
}));
largeJsonBuf = Buffer.from(JSON.stringify({ items, count: items.length }));
} catch (e) {}
}

function loadDatabase() {
try {
const Database = require('better-sqlite3');
const db = new Database('/data/benchmark.db', { readonly: true });
db.pragma('mmap_size=268435456');
dbStmt = db.prepare('SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN ? AND ? LIMIT 50');
} catch (e) {}
}

function sumQuery(query) {
let sum = 0;
if (query) {
for (const key of Object.keys(query)) {
const n = parseInt(query[key], 10);
if (n === n) sum += n;
}
}
return sum;
}

function startWorker() {
loadDataset();
loadLargeDataset();
loadStaticFiles();
loadDatabase();

const express = require('express');
const app = express();

// Raw body parsing
app.use(express.raw({ type: 'application/octet-stream', limit: '50mb' }));
app.use(express.text({ type: 'text/plain', limit: '50mb' }));
app.use(express.raw({ type: '*/*', limit: '50mb' }));

// --- /pipeline ---
app.get('/pipeline', (req, res) => {
res.set('server', SERVER_NAME).type('text/plain').send('ok');
});

// --- /baseline11 GET & POST ---
app.get('/baseline11', (req, res) => {
const s = sumQuery(req.query);
res.set('server', SERVER_NAME).type('text/plain').send(String(s));
});

app.post('/baseline11', (req, res) => {
const querySum = sumQuery(req.query);
const body = typeof req.body === 'string' ? req.body : (req.body ? req.body.toString() : '');
let total = querySum;
const n = parseInt(body.trim(), 10);
if (n === n) total += n;
res.set('server', SERVER_NAME).type('text/plain').send(String(total));
});

// --- /baseline2 ---
app.get('/baseline2', (req, res) => {
const s = sumQuery(req.query);
res.set('server', SERVER_NAME).type('text/plain').send(String(s));
});

// --- /json ---
app.get('/json', (req, res) => {
if (!datasetItems) {
return res.status(500).send('No dataset');
}
const items = datasetItems.map(d => ({
id: d.id, name: d.name, category: d.category,
price: d.price, quantity: d.quantity, active: d.active,
tags: d.tags, rating: d.rating,
total: Math.round(d.price * d.quantity * 100) / 100
}));
const buf = Buffer.from(JSON.stringify({ items, count: items.length }));
res
.set('server', SERVER_NAME)
.writeHead(200, { 'content-type': 'application/json', 'content-length': buf.length });
res.end(buf);
});

// --- /compression ---
app.get('/compression', (req, res) => {
if (!largeJsonBuf) {
return res.status(500).send('No dataset');
}
const compressed = zlib.gzipSync(largeJsonBuf, { level: 1 });
res
.set('server', SERVER_NAME)
.set('content-type', 'application/json')
.set('content-encoding', 'gzip')
.set('content-length', compressed.length)
.send(compressed);
});

// --- /db ---
app.get('/db', (req, res) => {
if (!dbStmt) {
return res.set('server', SERVER_NAME).type('application/json').send('{"items":[],"count":0}');
}
let min = 10, max = 50;
if (req.query.min) min = parseFloat(req.query.min) || 10;
if (req.query.max) max = parseFloat(req.query.max) || 50;
const rows = dbStmt.all(min, max);
const items = rows.map(r => ({
id: r.id, name: r.name, category: r.category,
price: r.price, quantity: r.quantity, active: r.active === 1,
tags: JSON.parse(r.tags),
rating: { score: r.rating_score, count: r.rating_count }
}));
const body = JSON.stringify({ items, count: items.length });
res
.set('server', SERVER_NAME)
.set('content-type', 'application/json')
.set('content-length', Buffer.byteLength(body))
.send(body);
});

// --- /upload ---
app.post('/upload', (req, res) => {
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body || '');
res.set('server', SERVER_NAME).type('text/plain').send(String(body.length));
});

// Start HTTP/1.1 server
const server = http.createServer(app);
server.listen(8080, '0.0.0.0', () => {
startH2();
});
}

function startH2() {
const certFile = process.env.TLS_CERT || '/certs/server.crt';
const keyFile = process.env.TLS_KEY || '/certs/server.key';
try {
const opts = {
cert: fs.readFileSync(certFile),
key: fs.readFileSync(keyFile),
allowHTTP1: false,
};
const h2server = http2.createSecureServer(opts, (req, res) => {
const url = req.url;
const q = url.indexOf('?');
const p = q === -1 ? url : url.slice(0, q);
if (p.startsWith('/static/')) {
const name = p.slice(8);
const sf = staticFiles[name];
if (sf) {
res.writeHead(200, { 'content-type': sf.ct, 'content-length': sf.buf.length, 'server': SERVER_NAME });
res.end(sf.buf);
} else {
res.writeHead(404);
res.end();
}
} else {
// baseline h2
let sum = 0;
if (q !== -1) {
const qs = url.slice(q + 1);
let i = 0;
while (i < qs.length) {
const eq = qs.indexOf('=', i);
if (eq === -1) break;
let amp = qs.indexOf('&', eq);
if (amp === -1) amp = qs.length;
const n = parseInt(qs.slice(eq + 1, amp), 10);
if (n === n) sum += n;
i = amp + 1;
}
}
res.writeHead(200, { 'content-type': 'text/plain', 'server': SERVER_NAME });
res.end(String(sum));
}
});
h2server.listen(8443);
} catch (e) {
// TLS certs not available, skip H2
}
}

if (cluster.isPrimary) {
const numCPUs = os.availableParallelism ? os.availableParallelism() : os.cpus().length;
for (let i = 0; i < numCPUs; i++) cluster.fork();
} else {
startWorker();
}
Loading