Routing is the mechanism that connects an incoming URL to the code that should handle it. It is the backbone of any web APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses.. Get routing right and everything else slots into place; get it wrong and your codebase turns into a maze of conditionals. Express makes routing expressive and easy to read.
HTTPWhat is http?The protocol browsers and servers use to exchange web pages, API data, and other resources, defining how requests and responses are formatted. methods and what they mean
Every HTTP request has a method that signals intent. Express gives you a dedicated method for each one:
import express from 'express';
const app = express();
app.use(express.json());
// GET - read data, never modify anything
app.get('/articles', (req, res) => {
res.json([{ id: 1, title: 'Hello World' }]);
});
// POST - create a new resource
app.post('/articles', (req, res) => {
const article = { id: 2, ...req.body };
res.status(201).json(article);
});
// PUT - replace a resource entirely
app.put('/articles/:id', (req, res) => {
res.json({ id: req.params.id, ...req.body });
});
// PATCH - update part of a resource
app.patch('/articles/:id', (req, res) => {
res.json({ id: req.params.id, patched: true });
});
// DELETE - remove a resource
app.delete('/articles/:id', (req, res) => {
res.status(204).send();
});| Method | Intent | Typical status code on success |
|---|---|---|
| GET | Retrieve data | 200 OK |
| POST | Create a resource | 201 Created |
| PUT | Replace a resource | 200 OK |
| PATCH | Partially update | 200 OK |
| DELETE | Remove a resource | 204 No Content |
204 No Content means "it worked and there is nothing to send back." This is the correct response for a successful delete, you deleted the thing, so there is nothing to return.Route parameters
A route parameter is a named placeholder in the URL path. You define it with a colon (:) and Express captures whatever the client puts in that position.
// GET /users/42 → req.params.id === '42'
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: `User ${userId}` });
});
// Multiple parameters in one route
// GET /posts/7/comments/3
app.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});Notice that req.params.id is always a string, even when the URL contains a number. If you need to use it as a number (for a database query, for example), convert it explicitly: parseInt(req.params.id, 10).
Query strings
Query strings carry optional filtering, sorting, or paginationWhat is pagination?Splitting a large set of results into smaller pages so the server and client only handle a manageable chunk at a time. data. They live after the ? in the URL and are automatically parsed into req.query.
// GET /articles?page=2&limit=10&tag=express
app.get('/articles', (req, res) => {
const { page = '1', limit = '10', tag } = req.query;
res.json({
page: parseInt(page, 10),
limit: parseInt(limit, 10),
tag,
articles: []
});
});Think of query parameters as adjectives that modify the noun (the resource). /articles is the noun; ?tag=express&limit=5 narrows the result set without changing the meaning of the URL.
Chaining handlers with next()
A single route can have more than one handler. Each one receives next as a third argument. Calling next() hands control to the following handler in the chain. This is the foundation of Express middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it., which you will explore in a later lesson.
app.get('/secure-data',
(req, res, next) => {
// Step 1: check if user is logged in
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Not authorized' });
}
next(); // all good, continue
},
(req, res) => {
// Step 2: send the actual data
res.json({ secret: 'Here is your data' });
}
);return res.json(...) rather than just res.json(...) is a good habit inside conditional blocks. The return stops the function from executing any further, preventing accidental calls to next() after you have already sent a response.Organizing routes with express.Router()
Once your app has more than a handful of routes, putting them all in index.js becomes unmanageable. express.Router() lets you split routes into separate files and then mount them under a prefix in your main app.
// routes/articles.js
import express from 'express';
const router = express.Router();
// These paths are relative to wherever the router is mounted
router.get('/', (req, res) => {
res.json([{ id: 1, title: 'Hello World' }]);
});
router.get('/:id', (req, res) => {
res.json({ id: req.params.id });
});
router.post('/', (req, res) => {
res.status(201).json({ id: 2, ...req.body });
});
export default router;// index.js
import express from 'express';
import articlesRouter from './routes/articles.js';
const app = express();
app.use(express.json());
// Mount the router - all routes inside get the /articles prefix
app.use('/articles', articlesRouter);
app.listen(3000);Now GET /articles maps to router.get('/') and GET /articles/42 maps to router.get('/:id'). The router file only knows about paths relative to its mount point, which makes it reusable and easy to test in isolation.