Routing
Routing decides which code answers which URL. Express makes it declarative — app.get("/products/:id", handler) — and Router lets each feature keep its routes in its own file.
On this page · 6 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 6 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Learning objectives#
- Define routes for each HTTP method
- Read route params and query strings
- Group routes with express.Router
- Understand route order and add a 404 handler
Explanation#
app.get, app.post, app.put, app.patch and app.delete register a handler for a method and path. app.route("/products").get(...).post(...) groups handlers for one path.
Route parameters capture parts of the path: /products/:id → req.params.id (a string). Query strings (?page=2&q=pen) arrive in req.query.
express.Router() creates a mini-app with its own routes. Mount it with app.use("/api/products", productsRouter) — paths inside the router are relative to the mount point.
Express checks routes in the order they were added and uses the first match, so put specific paths (/products/featured) before generic ones (/products/:id).
A final app.use((req, res) => res.status(404)...) catches everything no route handled. In Express 5, wildcards must be named (/*splat), and async handlers that throw are sent to the error handler automatically.
Syntax#
const router = express.Router();
router.get('/', listProducts); // GET /api/products
router.get('/:id', getProduct); // GET /api/products/7
router.post('/', createProduct); // POST /api/products
app.use('/api/products', router);
Code example: A products router mounted under /api#
import express from 'express';
const products = [
{ id: 1, name: 'Pen', price: 20, category: 'stationery' },
{ id: 2, name: 'Backpack', price: 1200, category: 'bags' },
{ id: 3, name: 'Notebook', price: 60, category: 'stationery' },
];
// routes/products.js
const productsRouter = express.Router();
productsRouter.get('/', (req, res) => {
const { category, sort } = req.query;
let list = category ? products.filter(p => p.category === category) : products;
if (sort === 'price') list = [...list].sort((a, b) => a.price - b.price);
res.json(list.map(p => p.name));
});
productsRouter.get('/featured', (req, res) => { // before /:id on purpose
res.json({ featured: products[1].name });
});
productsRouter.get('/:id', (req, res) => {
const product = products.find(p => p.id === Number(req.params.id));
if (!product) return res.status(404).json({ error: `Product ${req.params.id} not found` });
res.json(product);
});
// app.js
const app = express();
app.use('/api/products', productsRouter);
app.use((req, res) => res.status(404).json({ error: `Cannot ${req.method} ${req.path}` }));
const server = app.listen(3010, async () => {
for (const path of ['/api/products?category=stationery&sort=price', '/api/products/featured', '/api/products/3', '/api/products/9', '/api/orders']) {
const res = await fetch('http://localhost:3010' + path);
console.log(res.status, path, '→', await res.text());
}
server.close();
});
200 /api/products?category=stationery&sort=price → ["Pen","Notebook"]
200 /api/products/featured → {"featured":"Backpack"}
200 /api/products/3 → {"id":3,"name":"Notebook","price":60,"category":"stationery"}
404 /api/products/9 → {"error":"Product 9 not found"}
404 /api/orders → {"error":"Cannot GET /api/orders"}This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.
Step-by-step explanation#
- productsRouter holds all product routes; paths are relative to where it is mounted.
- req.query.category and req.query.sort filter and sort the list.
- /featured is registered before /:id, otherwise “featured” would be treated as an id.
- req.params.id is a string, so it is converted with Number().
- The final app.use catches any request no route handled and returns a JSON 404.
Real-world use case#
A shop API has routes/products.js, routes/orders.js and routes/users.js, each exporting a Router, and app.js mounts them under /api. Each team owns its own file.
Common mistakes#
If /:id comes before /featured, “featured” is captured as an id and returns 404.
req.params.id is "3", so p.id === req.params.id is false. Convert first.
app.get("*") throws in Express 5. Use app.get("/*splat") or a final app.use for 404s.
Best practices#
- One Router per resource
- Specific routes before parameter routes
- A JSON 404 handler at the end
- Validate params and query values
Remember this#
:id → req.params. ?q= → req.query. Both are strings.
express.Router() + app.use("/prefix", router).
First match wins — order matters.
Practice exercise#
Your task: Create an ordersRouter with GET / (returns []) and GET /:id (returns { id }), mount it at /api/orders, and test both.
Need a hint?
Show solution
import express from 'express';
const ordersRouter = express.Router();
ordersRouter.get('/', (req, res) => res.json([]));
ordersRouter.get('/:id', (req, res) => res.json({ id: Number(req.params.id) }));
const app = express();
app.use('/api/orders', ordersRouter);
const server = app.listen(3011, async () => {
console.log(await (await fetch('http://localhost:3011/api/orders')).text()); // []
console.log(await (await fetch('http://localhost:3011/api/orders/42')).text()); // {"id":42}
server.close();
});
Mini quiz#
Where is the value of :id in /products/:id?
Want more? Practise all Node.js MCQs · Node.js interview questions
My notes#
Saved in this browser.