Performance Basics
8 examples to get you started with Performance for Node.js backends - 6 basic and 2 intermediate.
Search across all documentation pages
8 examples to get you started with Performance for Node.js backends - 6 basic and 2 intermediate.
npm install -g clinic for profiling (see clinic.js Suite).hrtimeHigh-resolution timers beat Date.now() for latency measurement.
import { createServer } from "node:http";
const server = createServer((req, res) => {
const start = process.hrtime.bigint();
res.end("ok");
const elapsedMs = Number(process.hrtime.bigint() - start) / 1_000_000;
console.log(JSON.stringify({ path: req.url, durationMs: elapsedMs }));
});
server.listen(3000);process.hrtime.bigint() is monotonic - not affected by clock skew.Related: Load Testing - saturation and p95 under concurrency
Percentiles matter more than averages for API SLOs.
const durations: number[] = [];
function recordDuration(ms: number): void {
durations.push(ms);
if (durations.length > 10_000) durations.shift();
}
function p95(): number {
const sorted = [...durations].sort((a, b) => a - b);
const idx = Math.ceil(sorted.length * 0.95) - 1;
return sorted[idx] ?? 0;
}
// Expose via /metrics or log every N requestsRelated: Metrics that Matter - RPS, p95, error rate
Loop lag delays every concurrent client, not just one slow route.
import { monitorEventLoopDelay } from "node:perf_hooks";
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
setInterval(() => {
console.log(JSON.stringify({
event: "loop_delay",
p99Ms: histogram.percentile(99) / 1e6,
maxMs: histogram.max / 1e6,
}));
histogram.reset();
}, 10_000);monitorEventLoopDelay measures how late the loop runs scheduled work.Related: Detecting Event-Loop Blockage - attribution tooling
performance.mark for Hot PathsBuilt-in marks integrate with Chrome DevTools and clinic.js.
import { performance } from "node:perf_hooks";
async function fetchUser(id: string) {
performance.mark("fetchUser:start");
const user = await db.query("SELECT * FROM users WHERE id = $1", [id]);
performance.mark("fetchUser:end");
performance.measure("fetchUser", "fetchUser:start", "fetchUser:end");
return user;
}performance.getEntriesByName("fetchUser") returns duration arrays for analysis.Related: clinic.js Suite - flame graphs for hot paths
Know requests-per-second at target concurrency before changing code.
# Quick smoke (not a full load test)
npx autocannon -c 50 -d 10 http://localhost:3000/health-c 50 simulates 50 concurrent connections - closer to production than one curl.Related: Load Testing - k6 scenarios and saturation points
Large JSON bodies block parsing synchronously on the main thread.
import express from "express";
const app = express();
app.use(express.json({ limit: "1mb" }));
app.post("/events", (req, res) => {
// body already bounded - reject oversized at parser
res.status(201).json({ ok: true });
});limit returns 413 before your handler runs - saves CPU on abuse.Related: JSON Serialization Cost - response shape diet
Sustained concurrency reveals blocks that single requests hide.
// k6 script - save as load-test.js
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "1m", target: 50 },
{ duration: "3m", target: 50 },
{ duration: "1m", target: 0 },
],
thresholds: { http_req_duration: ["p(95)<200"] },
};
export default function () {
const res = http.get("http://localhost:3000/users");
check(res, { "status is 200": (r) => r.status === 200 });
sleep(0.1);
}thresholds fail the run when p95 exceeds 200ms - gate merges on regression.Related: Performance on Express - Express tuning under load
Suspected leaks need evidence before tuning --max-old-space-size.
import { writeHeapSnapshot } from "node:v8";
import { writeFileSync } from "node:fs";
function captureHeap(label: string): void {
const path = writeHeapSnapshot();
writeFileSync(`/tmp/heap-${label}.json`, ""); // marker file
console.log(JSON.stringify({ event: "heap_snapshot", label, path }));
}
// Call on SIGUSR2 or after N requests in staging
process.on("SIGUSR2", () => captureHeap("manual"));Related: Memory & GC Tuning -
--max-old-space-sizeand GC flags
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026