Introduction
As your API grows beyond a handful of endpoints, dumping everything into a single file becomes unmanageable fast. Express gives you a built-in tool called Router to split routes into separate modules by resource. Pair that with a controllers layer and you have a structure that scales cleanly — new team members can find what they need, and adding a new resource does not touch existing code.
This lesson walks through a concrete project layout: routes define the URL patterns and HTTP verbs, controllers hold the actual handler logic, and app.js wires them together. That separation is the single most important habit to build early.
Project Structure
Here is the directory layout we will build toward:
The pattern is simple: one route file per resource, one controller file per resource. Routes stay thin — they only declare which controller function handles each endpoint. Controllers stay focused — they only contain request/response logic for their resource.
Express Router
Express Router creates a mini-application that handles routes independently, then you mount it on a path in app.js.
Start with the users route file:
Notice the routes use relative paths like / and /:id, not /users or /users/:id. The prefix gets added when you mount the router in app.js, so you never repeat yourself.
Mounting the router in app.js:
A GET request to /users/42 now resolves to router.get('/:id', ...) with req.params.id equal to '42'. Clean and predictable.
Controllers
Controllers are plain modules that export named functions. Each function is a standard Express request handler — it receives req and res (and optionally next) and is responsible for sending a response.
Several things worth noting here. The controller validates input and returns early with an appropriate status code when something is wrong — this keeps the happy path readable. The 404 check is explicit: do not assume a record exists. And 204 No Content with no body is the conventional response for a successful delete.
Common Mistakes
Forgetting to parse JSON request bodies is the most frequent first-time issue. Without app.use(express.json()) in app.js, req.body will be undefined and createUser or updateUser will silently receive nothing.
Another pitfall is comparing req.params.id (a string) directly to a numeric id with strict equality. The users.find(u => u.id === req.params.id) check will always fail because '42' !== 42. Use parseInt or Number() to convert first.
Finally, not returning after sending a response leads to a 'Cannot set headers after they are sent' error. Always use return res.status(404).json(...) — the return prevents Express from continuing to the next line and attempting a second response.
Summary
Express Router lets you group related routes into their own modules and mount them at a prefix in app.js. Controllers keep handler logic out of route files, making each layer easier to read, test, and extend. The pattern is: route file declares the URL and verb, controller function handles the logic and sends the response. Stick with one file per resource and this structure will hold up as the project grows.
Lesson Checkpoint
Review what you learned and get feedback on your work.