One File Per Resource — Easy to Find, Easy to Touch
Per-Resource Route Files
Each resource gets its own routes file. Standard Express convention for production apps.
What you'll learn
- Organize routes by resource
- Compose them into the app
- Keep route files thin
The standard Express convention: one file per resource, each exporting a Router. The root app stitches them together.
A Typical Tree
src/routes/
├── users.js
├── posts.js
├── comments.js
├── auth.js
└── admin.js A Routes File
// src/routes/posts.js
import { Router } from "express";
import * as posts from "../controllers/posts.js";
const r = Router();
r.get("/", posts.list);
r.post("/", posts.create);
r.get("/:id", posts.get);
r.patch("/:id", posts.update);
r.delete("/:id", posts.remove);
r.get("/:id/likes", posts.listLikes);
r.post("/:id/likes", posts.like);
export default r; A routes file should be scannable. You can see every URL this resource exposes at a glance.
The Glue
// src/app.js
import express from "express";
import users from "./routes/users.js";
import posts from "./routes/posts.js";
import comments from "./routes/comments.js";
import auth from "./routes/auth.js";
export function buildApp() {
const app = express();
app.use(express.json());
app.use("/api/auth", auth);
app.use("/api/users", users);
app.use("/api/posts", posts);
app.use("/api/comments", comments);
return app;
} Adding a new resource: create the file, mount it. Two-line diff in
app.js.
Don’t Put Logic In Route Files
A route file should be just routing. No DB calls, no validation, no business logic. That goes in controllers and services (covered in the REST chapter).
A route file you can read in 10 seconds tells you the resource’s shape. A route file with inline logic tells you nothing fast.
Index-File Aggregation
For very large apps, an index.js per resource group:
src/routes/
├── index.js ← re-exports
├── users.js
├── posts.js
└── admin/
├── index.js
├── reports.js
└── users.js // src/routes/index.js
export { default as users } from "./users.js";
export { default as posts } from "./posts.js";
export { default as admin } from "./admin/index.js"; Reduces import noise in app.js. Optional — fine to skip until
needed.