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.

Create a free account to save this page, take notes and track your progress. Sign in

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 with app.use("/api/v1/products", router).
  • Path parameters (/:id) come from req.params; query strings (?category=books&limit=2) from req.query, always as strings.
  • Validate and default query values: missing, non-numeric or huge limit values 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

A mounted router with filters and pagination
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.limit blindly: ?limit=100000 returns the whole table.
  • Registering /:id before a more specific route like /search, so search is 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.

Was this page helpful?