Routing
Routing is how a server decides what to do with an incoming request. Every request has two key pieces of information:
- the HTTP method
- URL path
GET /api/users
───┬─── ────┬───
│ └── WHERE (route path)
└─────────── WHAT (HTTP method)
│
▼
Router matches → runs the correct handler function
Static Routes
Static routes have a fixed, unchanging path. Every part of the URL is a literal string.
GET /api/books → return all books
POST /api/books → create a new book
GET /api/users → return all users
Dynamic Routes
Dynamic routes contain variable segments in the URL path, denoted by a colon(:). These variables let you target a specific resource by its identifier. The server extracts the dynamic value from the URL and uses it in its logic
GET /api/users/:id
GET /api/users/123 → id = "123"
GET /api/users/456 → id = "456"
GET /api/users/abc → id = "abc"
Query Parameters
Query parameters are key-value pairs appended to the URL after a ?. They are used for optional, non-semantic data like filtering, sorting, and pagination. Multiple query params are separated by &. They are especially important for GET requests since GET has no request body
GET /api/books?page=2
GET /api/search?query=javascript&sort=newest
GET /api/products?category=shoes&minPrice=50&maxPrice=200
Nested Routes
Nested routes express hierarchical relationships between resources by embedding multiple identifiers in a single path
/api/users/123/posts/456
│ │
│ └── Post ID 456 belonging to user 123
└── User ID 123
/api → base path
/users → static, "users" collection
/123 → dynamic, specific user (path param)
/posts → static, "posts" collection under that user
/456 → dynamic, specific post (path param)
Route versioning
As APIs evolve, you often need to make breaking changes such as changing response shapes, renaming fields, removing endpoints. If you change an existing endpoint, every client using it breaks. Versioning solves this by running multiple versions of the API simultaneously
GET /api/v1/products → old format, still running for existing clients
GET /api/v2/products → new format, for new clients
Common versioning strategies beyond URL versioning include passing the version as a header API-Version: 2 or as a query param ?version=2, but URL versioning is the most common and explicit.
Catch-All Routes
A catch-all (wildcard) route is a fallback handler that matches any request that didn’t match any defined route. It prevents the server from returning a confusing raw error. For example the following code returns 404 on every route that is not registered before it.
app.all('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
Working of a router
When a request comes in, the router iterates through registered routes in order and pattern-matches the method + path:
Incoming: GET /api/users/123/posts
Registered routes:
GET /api/users → no
POST /api/users → no (wrong method)
GET /api/users/:id → no (path too long)
GET /api/users/:id/posts → yes MATCH → run handler
The router extracts dynamic segments, populates req.params, and calls the handler. If nothing matches, the catch-all fires.