Node.js · Web Servers

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.

Intermediate 6 min Lesson 20 of 37Examples target Node.js 22 LTS.
Before you start:Express.js
0/37 in Node.js
On this page · 6 min · Intermediate

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#

JavaScript Illustrative
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#

JavaScriptroutes.js Illustrative
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();
});
Expected output
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#

  1. productsRouter holds all product routes; paths are relative to where it is mounted.
  2. req.query.category and req.query.sort filter and sort the list.
  3. /featured is registered before /:id, otherwise “featured” would be treated as an id.
  4. req.params.id is a string, so it is converted with Number().
  5. 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#

Generic routes first

If /:id comes before /featured, “featured” is captured as an id and returns 404.

Treating params as numbers

req.params.id is "3", so p.id === req.params.id is false. Convert first.

Unnamed wildcards in Express 5

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?
express.Router(); app.use("/api/orders", ordersRouter).
Show solution
JavaScript Illustrative
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#

Routing quizMultiple choice Question 1 of 3

Where is the value of :id in /products/:id?

Score so far: 0/0

Want more? Practise all Node.js MCQs · Node.js interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.