Last 30 Days
No notifications
Express is a minimal, unopinionated web framework for Node.js. It simplifies routing, middleware, and request handling.
| Raw http module | Express |
| Manual routing | app.get('/path', handler) |
| Manual JSON parsing | express.json() middleware |
| Manual error handling | Built-in error handler |
| No middleware chain | Rich middleware ecosystem |
const express = require('express');
const app = express();// Parse JSON bodies
app.use(express.json());
// Define routes
app.get('/', (req, res) => {
res.json({ message: 'Hello World!' });
});
app.listen(3000, () => console.log('Server on port 3000'));
| Property | Description | Example | |||
req.params | Route parameters | /users/:id β req.params.id | |||
req.query | Query string | ?page=1 β req.query.page | |||
req.body | Request body | POST JSON data | |||
req.headers | Request headers | req.headers['authorization'] | |||
req.method | HTTP method | 'GET', 'POST' | Response Object (res) | Method | Description |
res.json(data) | Send JSON response | ||||
res.status(code) | Set status code | ||||
res.send(data) | Send any response | ||||
res.redirect(url) | Redirect client |
Node's built-in http module works, but writing a real API with it is painful β you'd parse URLs by hand, dispatch on method strings, and reinvent middleware. Express is a tiny framework on top of http that gives you:
app.get('/users', β¦))npm install express// server.js
import express from 'express';const app = express();
app.get('/', (req, res) => {
res.send('Hello from Express!');
});
app.listen(3000, () => console.log('http://localhost:3000'));
Three concepts:
express() builds the app object.app.get(path, handler) registers a route.app.listen(port) opens the socket.req and res ObjectsEvery handler receives req (incoming) and res (outgoing).
app.get('/users/:id', (req, res) => {
req.params.id // "42" (URL params)
req.query.page // "3" (?page=3)
req.body // { ... } (after express.json())
req.headers // { 'authorization': '...' }
req.method // "GET"
req.path // "/users/42"
req.ip // client IP
});res.send('hi'); // text/html
res.json({ ok: true }); // JSON
res.status(404).json({ ... }); // chain status + body
res.redirect('/login');
res.set('X-Total', '42'); // set header
res.cookie('session', 'abc');
res.sendFile('/path/to/file');app.get ('/users', listUsers);
app.post ('/users', createUser);
app.put ('/users/:id', replaceUser);
app.patch ('/users/:id', updateUser);
app.delete('/users/:id', deleteUser);Match an HTTP method + URL path β run a function. That's the whole programming model.
Express doesn't parse bodies by default β you opt in with built-in middleware:
app.use(express.json()); // parse application/json
app.use(express.urlencoded({ extended: true }));// parse form postsapp.post('/users', (req, res) => {
const { name, email } = req.body;
res.status(201).json({ id: 1, name, email });
});
1. req.body is undefined. You forgot app.use(express.json()). (Or the client forgot Content-Type: application/json.)
2. Sending a response twice. res.json(β¦) then res.send(β¦) throws Cannot set headers after they are sent. Always return after sending.
3. Forgetting return after res.status(404).json(β¦) β code below keeps running and sends a second response.
4. Using process.env.PORT without a fallback. app.listen(process.env.PORT || 3000) is the safe pattern.
5. Hard-coding origins for CORS. Use the cors package and a config-driven allowlist.
6. Putting all routes in server.js. Split by feature into routes/users.js, routes/posts.js once you have more than ~5.
Every request walks top to bottom through whatever you registered with app.use and app.METHOD:
incoming request
β
express.json() β parse body
cors() β add CORS headers
morgan('dev') β log
authMiddleware() β verify JWT, set req.user
router / handler β your route function runs
errorHandler(err,...) β only if next(err) was calledOrder matters: an auth middleware after the route can't protect it.
src/
βββ server.js β boot the app
βββ app.js β build the express app (testable!)
βββ config/
βββ middleware/
β βββ auth.js
β βββ errorHandler.js
βββ routes/
β βββ users.routes.js
β βββ posts.routes.js
βββ controllers/ β thin: parse req, call service, send res
βββ services/ β business logic
βββ models/ β DB layerRule of thumb: routes are dumb, services are smart. Controllers translate HTTP to function calls.
// routes/users.routes.js
import { Router } from 'express';
const router = Router();router.get('/', listUsers);
router.post('/', createUser);
router.get('/:id', getUser);
export default router;
// app.js
import usersRouter from './routes/users.routes.js';
app.use('/api/users', usersRouter);Routers are how you split a big API into small, mountable pieces.
Express 4 doesn't catch errors thrown from async functions. Two fixes:
// 1. try/catch + next
app.get('/users/:id', async (req, res, next) => {
try {
const u = await db.users.find(req.params.id);
if (!u) return res.status(404).json({ error: 'not found' });
res.json(u);
} catch (err) { next(err); }
});// 2. wrap once with a helper
const asyncH = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
app.get('/users/:id', asyncH(async (req, res) => { /* β¦ */ }));
Express 5 (now stable) finally handles thrown async errors automatically.
app.use((err, req, res, next) => {
const status = err.statusCode || 500;
res.status(status).json({
error: err.message,
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
});
});Four-arg signature β Express recognises it as an error handler. Always last.
helmet() β sane default security headers.compression() β gzip/brotli responses.express-rate-limit β throttle abusive IPs.cookie-parser + csurf β CSRF defence (when using cookies).pino-http or morgan β structured request logs.app.set('trust proxy', 1) so req.ip is correct.Deploy systems send SIGTERM before killing your container. Stop accepting new requests, finish in-flight ones, then exit:
const server = app.listen(3000);for (const sig of ['SIGINT', 'SIGTERM']) {
process.on(sig, () => {
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10_000).unref(); // hard kill after 10s
});
}
1. Build a 3-route Express app: GET /, GET /users, POST /users (in-memory array).
2. Move the user routes into routes/users.routes.js using Router.
3. Add express.json(), cors(), and a 404 handler at the end.
4. Add an async controller that throws on bad input and a central error handler that maps it to 400.