Routing
The way a server decides which piece of code should handle a given URL and HTTP method.
What is it?
A backend usually needs to do many different things: fetch a list of users, create a new order, delete a comment, and so on. Every incoming request arrives with a path (like /users or /orders/42) and a method (GET, POST, PUT, DELETE...) that together describe what the caller wants. The server needs a way to look at those two pieces of information and decide exactly which function should run.
That mapping — from "method + path" to "the function that handles it" — is called routing. Each individual mapping (like "a GET request to /users runs this function") is called a route.
Explain like I'm 10
A router is like the directory board in an office lobby: you tell it who you want to see and what you're there for (a delivery vs. a meeting), and it tells you exactly which floor and room to go to. Without it, you'd have to knock on every door in the building.
Examples
Basic routes by method and path
app.get("/users", (req, res) => {
res.json(allUsers);
});
app.post("/users", (req, res) => {
const newUser = createUser(req.body);
res.status(201).json(newUser);
});
app.delete("/users/:id", (req, res) => {
deleteUser(req.params.id);
res.status(204).end();
});The same path, /users, behaves completely differently depending on the HTTP method — routing is what tells them apart.
Route parameters and query strings
// GET /products/17?color=red
app.get("/products/:id", (req, res) => {
const productId = req.params.id; // "17"
const color = req.query.color; // "red"
res.json(findProduct(productId, color));
});Route parameters (:id) capture parts of the path itself, while query strings (?color=red) capture extra optional filters after a question mark.
How it works
When a request arrives, the framework walks through the routes you've registered, in the order you defined them, checking each one's method and path pattern against the incoming request. As soon as it finds a match, it runs that route's handler function and stops looking (unless that handler explicitly passes control onward). If nothing matches, the framework falls back to a default "not found" response.
Why does it exist?
Without routing, every request would have to be handled by one giant function containing endless if-statements checking the method and path by hand. Routing exists to let you declare, in a clear and organized way, exactly which code is responsible for which kind of request — making the codebase easier to navigate as it grows to dozens or hundreds of endpoints.
When to use it
Every backend endpoint you build needs a route: any time you want a client to be able to reach a specific piece of server logic via a specific URL and method, you define a route for it.
When not to use it
Routing doesn't apply to logic that isn't triggered by an incoming request — a scheduled background job or an internal helper function doesn't need a route, since nothing external is calling it by URL.
Common mistakes
Defining a more general route (like /users/:id) before a more specific one (like /users/me), causing the general one to match first and swallow requests meant for the specific one.
Forgetting that the same path can have entirely different meanings depending on the HTTP method.
Confusing route parameters (part of the path, like :id) with query strings (the ?key=value part after the path).
Practice exercises
- Easy:
Write a route that responds to a GET request at /ping with the text "pong".
- Medium:
Write a route /articles/:slug that reads the slug from the URL and returns it in a JSON response.
- Hard:
Explain why placing app.get("/users/:id") before app.get("/users/me") could cause a bug, and show how to fix the ordering.
Interview questions
What is routing in a web server?
The mechanism that maps an incoming request's method and URL path to the specific function that should handle it.
What's the difference between a route parameter and a query string?
A route parameter is a named part of the path itself, like :id in /users/:id, while a query string is optional key-value data appended after a question mark, like ?sort=asc.
What typically happens if no route matches an incoming request?
The framework falls back to a default handler, usually responding with a 404 Not Found status.
Why does the exact same URL path behave completely differently depending on the HTTP method used?
Because a route is defined as a combination of method and path together — GET /users and POST /users are two entirely separate routes that just happen to share a path, each pointing at its own handler.
Given `app.get("/users/:id", handler1)` registered before `app.get("/users/me", handler2)`, which handler runs for a GET request to `/users/me`, and why?
handler1 runs, because :id matches any value in that position including the literal text "me" — the framework checks routes in registration order and stops at the first match, so the more general pattern wins even though /users/me was probably meant to hit handler2.
How would you fix the ordering bug from the previous question so `/users/me` reaches the correct handler?
Register the more specific route, app.get("/users/me", handler2), before the more general app.get("/users/:id", handler1), so the exact match is checked first.
In `app.get("/users/:id", ...)`, what does `req.params.id` contain for a request to `/users/42`?
The string "42" — route parameters are captured as strings taken directly from the URL, not automatically converted to numbers or other types.
For a request to `/products/17?color=red`, what does `req.query` contain, and how does that differ from `req.params`?
req.query would be { color: "red" } — data from after the ?, typically optional filters or options — while req.params holds { id: "17" }, the value captured from the path pattern itself; params are usually required to identify a resource, query strings are usually optional refinements.
Why does a framework generally stop looking for further route matches once it finds the first one that matches?
Running a request through every matching route would be wasteful and ambiguous — a request should be handled once, so as soon as the framework finds the first route whose method and path match, it runs that handler and considers the request handled.
Why does it matter that a route to delete a resource uses the DELETE method rather than just reusing GET for everything?
Routing distinguishes intent by method as well as path, so using DELETE (rather than overloading GET) lets the exact same URL safely support multiple different operations, and lets tools, caches, and other developers understand what a request is meant to do without inspecting its body.
A route is defined as `app.post("/users", ...)`, but a client sends a GET request to `/users` and no other route matches. What happens?
The request doesn't match any registered route, since the method doesn't line up, so it falls through to the framework's default "not found" behavior, typically a 404 response.
Why is it a mistake to think of a route path as matching regardless of HTTP method?
A route registration always pairs a specific method with a specific path pattern; a path alone isn't a complete route, so /users registered only under GET simply won't match a POST request to the same path.
Why does the order in which routes are registered matter, if the framework is just checking each one against the request?
Because the framework stops at the first match rather than finding the best match, a more general pattern registered earlier can intercept requests meant for a more specific pattern registered later — order acts as an implicit priority.
A team registers `app.get("/articles/:slug", handler1)` before `app.get("/articles/featured", handler2)`. What bug will `/articles/featured` hit?
Requests to /articles/featured will always be caught by the :slug route first, since :slug matches the literal text "featured" just as well as any other value, so handler2 never runs.
What general rule of thumb avoids the "general route matches before specific route" bug?
Register more specific, literal routes before more general, parameterized ones that could also match the same requests.
Can a single path like `/users` have more than one route registered against it?
Yes — since a route is method plus path together, /users can have separate GET, POST, DELETE (and other) routes all registered against the same path, each with its own handler.
Why can a route parameter like `:id` match values you didn't expect, such as non-numeric text where you assumed a number, and what problem can that cause?
A route parameter captures whatever text appears in that segment of the URL with no automatic type or format checking, so /users/:id matches /users/abc just as readily as /users/42 — the handler must validate or convert the value itself, or it may crash or behave incorrectly on unexpected input.
What's the difference in purpose between a route parameter and a query string, beyond syntax — when would you reach for one over the other?
A route parameter typically identifies which resource you're operating on, so it's part of the path's identity (/orders/42); a query string typically expresses optional modifiers to a request that don't change which resource you're looking at, like sorting or filtering (/orders?status=shipped).
Is there a meaningful difference between `/users/:id` and `/users?id=42` achieving something similar?
Yes — conventionally, /users/:id communicates "this URL identifies one specific user resource," while /users?id=42 reads more like "list users, filtered by id"; the two forms carry different intent even when the underlying data returned might overlap.
Why does a framework need a "no routes matched" fallback at all, rather than assuming a route always exists?
Clients can request any arbitrary path, including typos, outdated URLs, or malicious probing — a server has no way to guarantee a matching route exists for every possible request, so it needs defined behavior for the case where nothing matches.
Walk through what happens, in order, when a `DELETE /users/42` request arrives.
The framework looks through registered routes in the order they were defined, checking each one's method and path pattern against DELETE and /users/42; when it reaches a route like app.delete("/users/:id", ...) whose method and pattern both match, it runs that handler with req.params.id set to "42" and stops looking further.
If two routes are registered for the exact same method and path — `app.get("/x", a)` then `app.get("/x", b)` — which one runs, and why might this be a bug?
Handler a runs, since it was registered first and the framework stops at the first match; b becomes dead code that never executes for that path, which is almost always an unintentional mistake rather than a deliberate design.
Why is routing described as replacing "one giant function with endless if-statements"? What would that alternative look like, and why is it harder to maintain?
Without routing, a single handler would need something like checking method === "GET" && url === "/users", then method === "POST" && url === "/users", and so on for every endpoint — as endpoints grow into the dozens, that one function becomes long, hard to navigate, and easy to introduce ordering or typo bugs in, whereas separate route declarations keep each endpoint's logic isolated and easy to locate.
Why doesn't routing logic apply to a scheduled background job, even though that job also runs backend code?
Routing exists specifically to map an incoming HTTP request's method and path to a handler; a background job isn't triggered by any incoming request or URL, so there's nothing for a route to match against — it's simply invoked directly, like any regular function call, on a timer or trigger.
How is routing conceptually similar to a JavaScript `switch` statement, and how is it different?
Both pick one branch to execute out of several based on matching some incoming value — but a switch typically matches a single value exactly, while routing matches on two dimensions at once (method and path pattern), and path patterns can include parameters that match a whole range of possible values rather than one exact literal.