Skip to content
Laviyx Learn

Try

Node.js and Express course menu

Express Routing: Routes, Parameters and express.Router

Organise Express routes: HTTP methods for CRUD, route parameters and multiple parameters, optional segments and wildcards in Express 5, route order, app.route for chaining, and splitting routes into modules with express.Router.

Intermediate8 min readUpdated
On this page

Routing decides which code handles each request, based on its method and path.

Methods#

Each HTTP method has a matching function: app.get, app.post, app.put, app.patch, app.delete, plus app.all for every method. In a REST API they map to actions on resources:

Method and pathMeaning
GET /api/bookslist books
GET /api/books/7get book 7
POST /api/bookscreate a book
PUT /api/books/7replace book 7
PATCH /api/books/7update some fields of book 7
DELETE /api/books/7delete book 7

Parameters#

A path segment starting with : is a route parameter. A route can have several:

params.js (JavaScript)
import express from "express";

const app = express();

app.get("/api/users/:userId/orders/:orderId", (req, res) => {
  res.json(req.params);
});

// Optional segment in braces: matches /api/reports and /api/reports/2026
app.get("/api/reports{/:year}", (req, res) => {
  res.json({ year: req.params.year ?? "all years" });
});

// Wildcard: *path captures the rest of the URL as an array of segments
app.get("/files/*path", (req, res) => {
  res.json({ segments: req.params.path, joined: req.params.path.join("/") });
});

app.listen(3000);
Terminal
curl -s localhost:3000/api/users/42/orders/1001
curl -s localhost:3000/api/reports
curl -s localhost:3000/api/reports/2026
curl -s localhost:3000/files/invoices/2026/oct.pdf
Output
{"userId":"42","orderId":"1001"}
{"year":"all years"}
{"year":"2026"}
{"segments":["invoices","2026","oct.pdf"],"joined":"invoices/2026/oct.pdf"}

The optional-segment and named-wildcard syntax is from Express 5. Express 4 used :year? and a bare *; you will see those in older tutorials, and they no longer work in Express 5.

Route order matters#

Express checks routes in the order they were added and uses the first match. Put specific paths before general ones:

order.js (JavaScript)
import express from "express";

const app = express();

app.get("/api/products/featured", (req, res) => res.json({ route: "featured list" }));
app.get("/api/products/:id", (req, res) => res.json({ route: "single product", id: req.params.id }));

app.listen(3000);
Terminal
curl -s localhost:3000/api/products/featured
curl -s localhost:3000/api/products/15
Output
{"route":"featured list"}
{"route":"single product","id":"15"}

If /api/products/:id came first, featured would be treated as an id.

Chaining with app.route#

app.route(path) groups the handlers for one path:

JavaScript
app.route("/api/books/:id")
  .get((req, res) => { /* read */ })
  .put((req, res) => { /* replace */ })
  .delete((req, res) => { /* delete */ });

express.Router: routes in separate files#

As an API grows, put each resource's routes in its own module with express.Router(), then mount it at a path:

routes/books.js (JavaScript)
import { Router } from "express";

const router = Router();
const books = [
  { id: 1, title: "Godan", author: "Premchand" },
  { id: 2, title: "Malgudi Days", author: "R. K. Narayan" },
];

router.get("/", (req, res) => res.json(books));

router.get("/:id", (req, res) => {
  const book = books.find((b) => b.id === Number(req.params.id));
  if (!book) return res.status(404).json({ error: "Book not found" });
  res.json(book);
});

router.delete("/:id", (req, res) => {
  const index = books.findIndex((b) => b.id === Number(req.params.id));
  if (index === -1) return res.status(404).json({ error: "Book not found" });
  books.splice(index, 1);
  res.status(204).end();
});

export default router;
server.js (JavaScript)
import express from "express";
import booksRouter from "./routes/books.js";

const app = express();
app.use("/api/books", booksRouter);      // every path in the router starts with /api/books

app.listen(3000);
Terminal
curl -s localhost:3000/api/books
curl -s localhost:3000/api/books/2
curl -s -X DELETE -o /dev/null -w "%{http_code}\n" localhost:3000/api/books/1
curl -s localhost:3000/api/books
curl -s localhost:3000/api/books/1
Output
[{"id":1,"title":"Godan","author":"Premchand"},{"id":2,"title":"Malgudi Days","author":"R. K. Narayan"}]
{"id":2,"title":"Malgudi Days","author":"R. K. Narayan"}
204
[{"id":2,"title":"Malgudi Days","author":"R. K. Narayan"}]
{"error":"Book not found"}

Inside the router, paths are relative to where it is mounted: router.get("/:id") handles /api/books/:id. 204 No Content is the usual response to a successful delete, with no body.

Common mistakes#

Practice#

Practice exercise

Create a router for /api/cities with GET / returning all cities and GET /:name returning one city by name, case-insensitively, or a 404 error. Mount it in an app and test GET /api/cities/PUNE.

Show hint

Compare city.name.toLowerCase() === req.params.name.toLowerCase().

Show solution
routes/cities.js (JavaScript)
import { Router } from "express";

const router = Router();
const cities = [
  { name: "Pune", state: "Maharashtra" },
  { name: "Mysuru", state: "Karnataka" },
];

router.get("/", (req, res) => res.json(cities));
router.get("/:name", (req, res) => {
  const city = cities.find((c) => c.name.toLowerCase() === req.params.name.toLowerCase());
  if (!city) return res.status(404).json({ error: "City not found" });
  res.json(city);
});

export default router;
cities-app.js (JavaScript)
import express from "express";
import citiesRouter from "./routes/cities.js";

const app = express();
app.use("/api/cities", citiesRouter);
app.listen(3000);
Terminal
curl -s localhost:3000/api/cities/PUNE
curl -s localhost:3000/api/cities/Delhi
Output
{"name":"Pune","state":"Maharashtra"}
{"error":"City not found"}

Key takeaway#

Next: Middleware.

Finished this lesson?