Node.js Backend Fundamentals with Express · Build an API with Express
Routers, Path Parameters and Query Strings
Organise routes with express.Router, read path and query parameters, and return proper 404s for unknown routes.
What you'll build
A /api/v1/products router mounted in the app, with filtering and pagination from the query string, a path parameter route, and a catch-all 404 for unknown paths.
The idea
express.Router()groups related routes; mount it withapp.use("/api/v1/products", router).- Path parameters (
/:id) come fromreq.params; query strings (?category=books&limit=2) fromreq.query, always as strings. - Validate and default query values: missing, non-numeric or huge
limitvalues should not crash the server or return everything. - Routes are matched in order; a final
app.use((req, res) => res.status(404)...)catches everything else.
The code
import express from "express";
const products = Array.from({ length: 7 }, (_, i) => ({ id: i + 1, name: `Item ${i + 1}`, category: i % 2 ? "books" : "games" }));
const router = express.Router();
router.get("/", (req, res) => {
const limit = Math.min(Math.max(Number.parseInt(req.query.limit ?? "3", 10) || 3, 1), 50);
const offset = Math.max(Number.parseInt(req.query.offset ?? "0", 10) || 0, 0);
const matching = products.filter((p) => !req.query.category || p.category === req.query.category);
res.json({ total: matching.length, items: matching.slice(offset, offset + limit).map((p) => p.id) });
});
router.get("/:id", (req, res) => {
const product = products.find((p) => p.id === Number(req.params.id));
return product ? res.json(product) : res.status(404).json({ error: `no product ${req.params.id}` });
});
const app = express();
app.use("/api/v1/products", router);
app.use((req, res) => res.status(404).json({ error: `no route for ${req.method} ${req.path}` }));
const server = app.listen(0);
const base = `http://127.0.0.1:${server.address().port}`;
for (const path of ["/api/v1/products?category=books&limit=2", "/api/v1/products?limit=abc&offset=6",
"/api/v1/products/4", "/api/v2/products"]) {
const r = await fetch(base + path);
console.log(path, "->", r.status, JSON.stringify(await r.json()));
}
server.close();
Output
/api/v1/products?category=books&limit=2 -> 200 {"total":3,"items":[2,4]}
/api/v1/products?limit=abc&offset=6 -> 200 {"total":7,"items":[7]}
/api/v1/products/4 -> 200 {"id":4,"name":"Item 4","category":"books"}
/api/v2/products -> 404 {"error":"no route for GET /api/v2/products"}Run this on your computer — see “Get Started” for setup.
Common mistakes
- Trusting
req.query.limitblindly:?limit=100000returns the whole table. - Registering
/:idbefore a more specific route like/search, sosearchis treated as an id. - Express's default 404 is an HTML page; APIs should return JSON errors.
Key points
- Group routes with Router and mount them under a prefix (with an API version).
- Path parameters and query values are strings: parse, validate and clamp them.
- Add a JSON 404 handler after all routes.
Further reading: express.Router · req.query
Practice questions
Why clamp a limit query parameter?
Answer
Without bounds a client can request huge pages (or invalid values), causing slow queries and large responses; parse it, apply a default and enforce a maximum.
Why does route order matter in Express?
Answer
Routes are matched in the order they are registered; a generic /:id route registered before /search would capture 'search' as an id.
How do you return JSON for unknown routes?
Answer
Add a final app.use((req, res) => res.status(404).json({...})) after all routes, because Express's default 404 response is HTML.
Open in the app to mark lessons complete, take notes and track your progress.