Express.js Cheat Sheet
Covers building routes, applying middleware, handling errors in async handlers, and key Express request/response methods.
Basic App & Routing
Setting up routes and parsing JSON bodies.
const express = require('express');const app = express();app.use(express.json()); // parse JSON request bodiesapp.get('/users/:id', (req, res) => { res.json({ id: req.params.id });});app.post('/users', (req, res) => { const { name } = req.body; res.status(201).json({ name });});app.listen(3000, () => console.log('Server running on port 3000'));
Middleware
Custom, router-level, and third-party middleware.
// Custom middlewarefunction logger(req, res, next) { console.log(`${req.method} ${req.url}`); next(); // pass control to the next handler}app.use(logger);// Router-level middlewareconst router = express.Router();router.use((req, res, next) => { if (!req.headers.authorization) return res.sendStatus(401); next();});router.get('/profile', (req, res) => res.json({ ok: true }));app.use('/api', router);// Third-party middlewareconst cors = require('cors');app.use(cors());
Error Handling & Async Routes
Forwarding errors to Express's error middleware.
// Async route (Express 4 does not catch async errors automatically)app.get('/data', async (req, res, next) => { try { const data = await fetchData(); res.json(data); } catch (err) { next(err); // forward to error-handling middleware }});// Error-handling middleware must declare all 4 argumentsapp.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Internal Server Error' });});
Key Methods & Concepts
Frequently used request/response APIs.
- app.use(path, fn)- mounts middleware, optionally scoped to a path prefix
- req.params / req.query / req.body- route params, query string, and parsed request body
- res.status().json()- chainable response builder for setting status and sending JSON
- express.static('public')- serves static files from a directory
- next(err)- skips remaining route middleware and jumps to error-handling middleware
- app.route(path)- chains multiple HTTP method handlers for a single path
app.param() for Route Parameter Preprocessing
Centralize parameter validation and resource loading so every route sharing that param benefits automatically.
app.param('userId', async (req, res, next, id) => { if (!/^[0-9a-f]{24}$/.test(id)) { return res.status(400).json({ error: 'Invalid userId format' }); } try { req.user = await db.users.findById(id); if (!req.user) return res.status(404).json({ error: 'User not found' }); next(); } catch (err) { next(err); }});// Every route with :userId now gets req.user preloaded and validatedapp.get('/users/:userId', (req, res) => res.json(req.user));app.get('/users/:userId/orders', (req, res) => res.json(req.user.orders));
Custom Error Classes & Centralized Handling
Distinguish operational (expected) errors from programmer errors so the error middleware can respond correctly.
class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); }}class NotFoundError extends AppError { constructor(resource) { super(`${resource} not found`, 404); }}app.get('/orders/:id', async (req, res, next) => { const order = await db.orders.findById(req.params.id); if (!order) return next(new NotFoundError('Order')); res.json(order);});app.use((err, req, res, next) => { const status = err.isOperational ? err.statusCode : 500; if (!err.isOperational) console.error('Unexpected error:', err); res.status(status).json({ error: err.isOperational ? err.message : 'Internal Server Error' });});
Streaming Large Responses
Pipe data directly to the response to avoid buffering entire payloads in memory, respecting backpressure.
const { pipeline } = require('stream');const { createReadStream } = require('fs');app.get('/export.csv', (req, res) => { res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename="export.csv"'); pipeline(createReadStream('./data/export.csv'), res, (err) => { if (err) { console.error('Streaming failed:', err); if (!res.headersSent) res.status(500).end(); } });});
Production Hardening Checklist
Common third-party middleware and settings for a hardened Express deployment.
- helmet()- sets security-related HTTP headers (CSP, HSTS, X-Frame-Options, etc.) in one call
- compression()- gzip/brotli-compresses response bodies
- express-rate-limit- throttles requests per IP/key to mitigate abuse and brute-force attacks
- app.set('trust proxy', 1)- tells Express to trust X-Forwarded-* headers when running behind a reverse proxy or load balancer
- morgan('combined')- Apache-style structured HTTP request logging
- app.disable('x-powered-by')- removes the header that fingerprints the app as Express
- cors({ origin, credentials })- scopes cross-origin access instead of allowing all origins
Nested Routers with mergeParams
Access parent router path parameters inside a nested router by opting in explicitly.
const commentsRouter = express.Router({ mergeParams: true });commentsRouter.get('/', (req, res) => { // req.params.postId is available here even though this router // is mounted under /posts/:postId/comments res.json({ postId: req.params.postId, comments: [] });});const postsRouter = express.Router();postsRouter.use('/:postId/comments', commentsRouter);app.use('/posts', postsRouter);
In Express 4.x, unhandled promise rejections inside async route handlers are not caught automatically: always wrap async handlers in try/catch and call next(err), or use a helper like express-async-handler (Express 5 fixes this natively).