Performance Mistakes

Common Express Performance Mistakes

Almost every slow Express 24,430 route is slow for one of a handful of reasons, and the commonest is startup work done per request. The route below reads a catalog file, parses it and filters it on every call; the fix does all three once. Both handlers live in the same app, so one load test measures both.

The same answer, computed per request and once at startupJavaScript
app.get('/slow', (req, res) => {                    // read, parse, filter: every request
  const all = JSON.parse(fs.readFileSync('catalog.json', 'utf8'));
  res.json({ data: all.filter((b) => b.year > 1980) });
});
const all = JSON.parse(fs.readFileSync('catalog.json', 'utf8'));   // 400 records, once
const modern = all.filter((b) => b.year > 1980);
app.get('/fast', (req, res) => res.json({ data: modern }));
Output
$ npx autocannon -c 50 -d 5 http://127.0.0.1:4303/slow
│ Latency │ 14 ms │ 22 ms │ 33 ms │ 36 ms │ 23 ms │ ...
│ Req/Sec │ 2,067 │ 2,067 │ 2,137 │ 2,163 │ 2,125 │ ...
$ npx autocannon -c 50 -d 5 http://127.0.0.1:4303/fast
│ Latency │ 3 ms │ 6 ms │ 10 ms │ 12 ms │ 6.01 ms │ ...
│ Req/Sec │ 6,491 │ 6,491 │ 8,079 │ 8,327 │ 7,690.8 │ ...

Moving two lines out of the hot path took the i9-7980XE from 2,125 to 7,691 requests per second — 3.6 times the throughput — and the median latency from 22 ms to 6 ms. readFileSync blocks the event loop, so while one request waits on the disk every other connection waits too, which is why the 99th percentile (36 ms) sits so far above the median. Any synchronous call in a handler behaves this way: fs.*Sync, crypto.pbkdf2Sync, zlib.gzipSync, JSON.parse of a megabyte.

Six mistakes that cost throughput, and where the fix is covered
Mistake Why it hurts Fix
Sync I/O or crypto in a handler blocks every connection async API, worker thread
Middleware mounted app-wide runs for unrelated routes mount it on one router
console.log per request synchronous write to a TTY pino 18,227 (Structured Logs with Pino)
Body parsing before limits you parse attacker payloads limiter first (Rate Limiting)
Unbounded list page size one client asks for it all cap limit (Pagination)
NODE_ENV left unset no view cache, verbose errors set it to production

The last row is free money in a server-rendered app: NODE_ENV=production makes Express cache compiled templates instead of recompiling on every render. Profile before guessing — Measure Before You Optimize finds the hot frame.