The Tweaks That Actually Move the Needle
Performance
Measure first. The wins in Express almost always live in the database and the event loop.
What you'll learn
- Profile before optimizing
- Avoid the common Express slowdowns
- Know the diminishing returns
The first rule: measure. The biggest gains in Express almost always live in the database, not Express itself.
Profile
node --inspect src/index.js Connect Chrome DevTools → Profiler tab → start. Fire some real requests. Stop. Read the flame chart. The biggest blob is your target.
For automated benchmarks, clinic is great:
npm install -g clinic
clinic doctor -- node src/index.js Sends synthetic load, diagnoses (CPU-bound? event-loop blocked? GC pressure?).
The Quick Wins
1. Compression
app.use(compression()); ~70% smaller JSON responses. Free. Done.
2. Avoid Sync IO
Never call *Sync fs methods, bcrypt.hashSync, or sync crypto
in a handler. Each blocks the event loop = every other request
waits.
3. Connection Pooling
DB and Redis: pool. Always. Opening one connection per request is disaster.
4. Streaming Big Responses
res.json(huge) allocates huge × 2 (the object, then the string).
Stream NDJSON or use a cursor.
5. Pagination Cap
?limit=999999 returning 1M rows ruins everyone’s day. Cap at 100.
6. Cache Reads
Hot lookups (config, user profiles, rate limits) — cache in Redis with short TTLs. The DB stays calm.
What’s Usually NOT The Problem
- Express itself — it’s fast enough; Fastify would be 30% faster on hello-world but it’s not your bottleneck
- JSON parsing —
express.json()is fine - Middleware count — 20 middlewares is not your problem
- V8 itself — almost certainly
The numbers usually tell you it’s the database or a single slow call in the path.
Common Slow Code Smells
// 🚨 N+1 query
const posts = await db.posts.findMany();
for (const p of posts) {
p.author = await db.users.findById(p.authorId); // 100 queries!
} Fix: join or batch.
// 🚨 unbounded list
app.get("/posts", (req, res) => {
res.json(db.posts.findMany()); // returns ALL posts
}); Fix: cap with limit.
// 🚨 sync hashing
const hash = bcrypt.hashSync(password, 12); Fix: async (await bcrypt.hash(...)).
When To Switch Frameworks
After exhausting the above, if you’ve micro-benchmarked Express itself as the bottleneck (rare), consider:
- Fastify — ~3x throughput on hello-world, schema-driven
- Hono — even faster, web-standards, runs on edge
- uWebSockets.js — extreme throughput, drops Node http
But the rule of optimization still applies: measure before switching. Most Express apps that “feel slow” have a database problem.
Cluster Mode →