Skip to main content

Express Cheatsheet

At a glance​

What it isMinimal, unopinionated web framework for Node.js built around routing and a middleware chain
Use it forREST/JSON APIs, server-rendered sites with a view engine, BFFs, prototypes, the HTTP layer under NestJS
Latest stablev5.3.0 (released 2026-10-09) - checked 2026-10-31
Official docsexpressjs.com · API 5.x · Migrating to Express 5 · History.md

Core concepts​

  • App: express() creates the application; app.listen() starts an HTTP server.
  • Routing: app.METHOD(path, ...handlers) matches HTTP method + path.
  • Middleware: functions (req, res, next) that run in registration order; call next() or send a response.
  • Router: express.Router() is a mini-app for grouping routes, mounted with app.use("/prefix", router).
  • Request (req): params, query, body, headers, cookies (with cookie-parser).
  • Response (res): json, send, status, redirect, render, sendFile, set.
  • Error middleware: 4 arguments (err, req, res, next), registered after routes.
  • Async errors (v5): a rejected promise from a handler is passed to next(err) automatically.

Syntax & commands​

npm init -y
npm install express # v5 is the "latest" tag
npm install -D @types/express # TypeScript types
node --watch server.js # dev reload without nodemon (Node 18.11+)
// server.js (ESM: set "type": "module" in package.json)
import express from "express";

const app = express();
app.use(express.json()); // parse JSON bodies
app.use(express.urlencoded({ extended: false })); // parse form bodies
app.use(express.static("public")); // serve static files

app.get("/health", (req, res) => res.json({ ok: true }));

app.get("/users/:id", async (req, res) => {
const user = await db.users.find(req.params.id); // errors go to error handler (v5)
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
});

app.post("/users", async (req, res) => {
const user = await db.users.create(req.body);
res.status(201).location(`/users/${user.id}`).json(user);
});

app.listen(3000, () => console.log("http://localhost:3000"));
NeedAPI
Path paramsreq.params.id (route /users/:id)
Query stringreq.query.page (read-only getter in v5)
Bodyreq.body (needs express.json(); undefined if not parsed in v5)
Headerreq.get("Authorization") / res.set("X-Id", "1")
Status + JSONres.status(201).json(data)
No contentres.sendStatus(204)
Redirectres.redirect(301, "/new")
Fileres.sendFile(path.resolve("file.pdf")) / res.download(p)
Cookieres.cookie("sid", v, { httpOnly: true }) / res.clearCookie("sid")
Viewapp.set("view engine", "ejs"); res.render("home", { title })

Common patterns​

// routes/users.js - Router module
import { Router } from "express";
const router = Router();

router.route("/")
.get(listUsers)
.post(validate(createUserSchema), createUser);
router.get("/:id", getUser);

export default router;

// app.js
app.use("/api/users", router);

Middleware by scope (Express docs: application-level, router-level, error-handling, built-in and third-party):

// Middleware: logging, auth, validation
const logger = (req, res, next) => {
const start = Date.now();
res.on("finish", () => console.log(req.method, req.originalUrl, res.statusCode, Date.now() - start, "ms"));
next();
};

const requireAuth = (req, res, next) => {
const token = req.get("Authorization")?.replace("Bearer ", "");
if (!token) return res.status(401).json({ error: "Unauthorized" });
req.user = verifyJwt(token);
next();
};

const validate = (schema) => (req, res, next) => {
const result = schema.safeParse(req.body); // e.g. Zod
if (!result.success) return res.status(400).json(result.error.flatten());
req.body = result.data;
next();
};
app.use(logger); // every request
app.get("/me", requireAuth, getMe); // one route
// Express 5 path syntax (path-to-regexp v8)
app.get("/files/*splat", handler); // wildcard must be named (was "/files/*")
app.get("/*splat", handler); // catch-all (does not match "/")
app.get("/users{/:id}", handler); // optional segment (was "/users/:id?")
app.get("/assets/{*path}", handler); // optional wildcard, matches "/assets" too

Gotchas​

  • Order matters: middleware and routes run in registration order; register body parsers before routes and the error handler last.
  • Respond exactly once: forgetting return before res.json() in a branch leads to "Cannot set headers after they are sent".
  • No body without a parser: req.body is undefined unless express.json()/urlencoded() ran (v4 gave {}).
  • Express 5 route strings changed: bare *, :id? and regex characters inside strings throw; use named wildcards and braces.
  • Express 4 async handlers swallow rejections; wrap them or upgrade to v5, which forwards rejected promises to next(err).
  • Nested query strings (a[b]=1) are not parsed in v5 by default; opt back in with app.set("query parser", "extended").
  • req.query is read-only in v5: write parsed values to another property (res.locals.query) instead of reassigning.
  • app.set("trust proxy", ...) is required behind proxies for correct req.ip, req.protocol and secure cookies.
  • Removed in v5: app.del, res.sendfile (use sendFile), req.param(), res.json(obj, status) signatures, and res.redirect("back") (use req.get("Referrer")).

What's new in recent releases​

  • 5.3.0 (2026-10-09): security upgrades of qs, proxy-addr and body-parser (an invalid limit now throws instead of silently disabling the size check); res.send() no longer sets Content-Length together with Transfer-Encoding.
  • 5.3.0: req.fresh supports the QUERY method; default error handler logs the full error (including cause); content-disposition 2 drops quotes around token-safe file names in res.attachment()/res.download().
  • 5.2.0 (2025-12-01): body-parser 2.2.1 and a deprecation warning for res.redirect() called with undefined arguments.
  • 5.1.0 (2025-03-31): Express 5 becomes the npm latest tag; Uint8Array in res.send(), ETag option for res.sendFile(), several dependencies dropped.
  • 5.0.0 (2024-09-10): Node.js 18+, async error forwarding, path-to-regexp v8 route syntax, "simple" query parser by default, removal of long-deprecated APIs. Express 4 continues on the latest-4 tag (4.22.x).

In these notes​