Performance

The Tweaks That Actually Move the Needle

Performance

Measure first. The wins in Express almost always live in the database and the event loop.

4 min read Level 2/5 #express#performance
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 parsingexpress.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 →