A route in Express is a mapping from (HTTP method, URL path) to a handler function. When a request comes in, Express finds the first matching route and runs its handler.
In simple language — “when someone does a GET on /users, run this function.” That’s it.
The shape
app.METHOD(PATH, HANDLER);
METHOD— http verb in lowercase:get,post,put,delete,patchPATH— URL pattern:/users,/users/:id,/api/*HANDLER—(req, res) => { ... }
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
res.json([{ id: 1, name: 'Manish' }]);
});
app.post('/users', (req, res) => {
res.status(201).json({ id: 2, name: 'New User' });
});
app.put('/users/:id', (req, res) => {
res.json({ id: req.params.id, updated: true });
});
app.delete('/users/:id', (req, res) => {
res.status(204).end();
});
app.listen(3000);
Sending responses
The res object has helpers for every common case. You only need to call one of these per request — calling two throws “Cannot set headers after they are sent.”
res.send('Hello'); // auto-detects content type
res.json({ ok: true }); // sets Content-Type: application/json
res.status(404).json({ ... }); // chainable status code
res.sendStatus(204); // sets status + sends status text
res.redirect('/login'); // 302 redirect
res.redirect(301, '/new'); // permanent redirect
res.end(); // no body, just close
Status code defaults to 200. We set it explicitly for created (201), no content (204), errors (4xx/5xx).
GET /users/42
route
res.json(...)
Order matters
Express checks routes top to bottom. The first one that matches wins. So a generic catch-all should always go last.
app.get('/users/me', (req, res) => res.json({ self: true }));
app.get('/users/:id', (req, res) => res.json({ id: req.params.id }));
// If we flipped these, /users/me would match :id first and return { id: 'me' }
Multiple handlers per route
We can pass multiple functions — they run in order, each calling next() to pass control:
const auth = (req, res, next) => {
if (!req.headers.authorization) return res.sendStatus(401);
next();
};
app.get('/profile', auth, (req, res) => {
res.json({ name: 'Manish' });
});
This is just middleware — covered in detail later. Useful for per-route auth checks.
app.all() and route chaining
For all methods on a path, use app.all. To group methods on one path cleanly, use app.route:
app.route('/books')
.get((req, res) => res.json([]))
.post((req, res) => res.status(201).json({}))
.put((req, res) => res.json({}));
This is mostly stylistic — same as writing three separate app.get/post/put calls.
What about async handlers?
Express 4 doesn’t auto-catch rejected promises. If we throw inside async (req, res), the request hangs forever:
// BROKEN in Express 4
app.get('/users/:id', async (req, res) => {
const user = await db.findUser(req.params.id); // if this rejects, request hangs
res.json(user);
});
Fix it with try/catch and next(err), or use a wrapper like express-async-errors. Express 5 handles this natively — but most production apps are still on v4.