SSiddiq.in
Backend engineering, not framework tourism

Node scales beautifully. Until one `for` loop blocks the event loop for 800ms.

We've been the on-call for enough Node backends to have opinions. This page is the runtime behaviour we design around — the event loop, backpressure, timeouts, and the moments where reaching for a different tool is the professional answer.

The event loop, honestly

One thread, one queue, one bad function.

Call stack (JS)
handleRequest()
parseHugeJSON() ← 800ms
everything else waits
→
Task queue
GET /users (queued 620ms)
POST /orders (queued 480ms)
Health check (queued 210ms) 💀

Every synchronous CPU-bound function blocks every other user. This is why "Node is slow" reports are almost always "someone put a sync JSON parse in the hot path". Move it to a worker, stream it, or precompute.

The 3am playbook

Nine defaults we set before we write a single route.

Request timeout · 15s

Nothing takes longer. If it might, it's a background job.

External call timeout · 3s

With one retry, jittered backoff, then circuit-break.

DB connection pool · sized

Never unbounded. Postgres has ~100 slots; we plan against it.

Body size limit · 1MB default

Uploads go through pre-signed URLs to object storage, not our RAM.

Rate limit at ingress

Per-IP + per-token. Before the app burns CPU parsing junk.

Structured logs w/ request-id

One id through gateway → app → DB → external. Debugging without it is guessing.

Graceful shutdown · SIGTERM

Drain the socket, finish inflight, then exit. Kubernetes will thank you.

Health check ≠ readiness check

Health = process alive. Ready = deps reachable. They are not the same.

Sentry + PagerDuty wiring

Errors route somewhere a human sees them. Otherwise they don't exist.

Where we deploy Node — and why

Serverless, edge, VM. Right tool for the shape of traffic.

PlatformBest forWatch out for
Vercel / Netlify FunctionsSpiky HTTP APIs behind a Next.js siteCold starts, 10s function limit, no long connections
Cloudflare Workers (Node compat)Global-edge APIs, low-latency readsNot full Node — check every dep
Fly.io / RailwayLong-running processes, WebSockets, cronYou do the ops (which is often the right choice)
AWS ECS / FargateEnterprise / VPC / SOC2 environmentsAWS complexity tax is real
Bare EC2 / HetznerPredictable heavy load, cost-sensitiveYou now run a Linux server. Have a plan.

Node.js FAQ

The JS execution is single-threaded. The I/O layer (libuv) is a thread pool. Most bottlenecks are in-JS work — parsing giant JSON, sync crypto, unbounded loops — not the network. Move CPU-heavy work to a worker thread or a separate service.

Memory leak patterns

Seven ways a Node process quietly runs out of memory.

Unbounded caches
`const cache = {}` that never evicts. Use LRU with a hard entry cap; log evictions.
Event listener leaks
`emitter.on()` added per request without `off()`. `MaxListenersExceededWarning` is a code smell, not a warning to ignore.
Timer / interval leaks
`setInterval` in a per-request handler. Every request adds a timer that never clears. Use AbortController + timeouts.
Closures holding requests
Async callback captures the full request object. Long-lived promise = req + res never GC'd. Extract only what you need.
Unconsumed streams
Response stream never `.pipe`d or `.destroy`ed on error. Buffered chunks accumulate. Always end streams in error paths.
Global growth
Pushing to `global.errors = []` for 'debugging'. Ship, forget, OOM in production week 3.
Native module handles
DB clients, file descriptors, worker threads not closed on shutdown. Trap SIGTERM + drain gracefully.

A Node backend that doesn't wake you up.

Scope a backend
Right tech, honest advice

Not sure if this is the right stack for you?

Tell us your goals — we'll recommend the right technology honestly, even if it isn't this one.

  • Personal reply from Siddiq within 24 hours
  • Honest advice — no pushy sales, no fake urgency
  • Clear next steps, timeline, and estimate

We only use your details to reply to this enquiry. Privacy.