Routers and project structure
Split an API into files that a team can find their way around.
Once an API has more than a few routes, put each area in its own router file. server.js then only joins the pieces together.
Node.js
// routes/jobs.js
import { Router } from "express";
import { findJob, listJobs } from "../services/jobs.js";
export const jobs = Router();
jobs.get("/", async (req, res) => {
res.json(await listJobs());
});
jobs.get("/:id", async (req, res) => {
const job = await findJob(req.params.id);
if (!job) return res.status(404).json({ error: "Job not found" });
res.json(job);
});
// app.js
import express from "express";
import { jobs } from "./routes/jobs.js";
export const app = express();
app.use(express.json());
app.use("/api/jobs", jobs);
// server.js
import { app } from "./app.js";
const port = process.env.PORT ?? 3000;
app.listen(port, () => console.log(`Listening on ${port}`));Express 5 passes errors from async handlers to your error middleware. Express 4 does not, so on version 4 wrap async handlers in try/catch.
HTML
my-api/
app.js builds the app (tests import this)
server.js starts it
routes/ one file per area: jobs.js, users.js
services/ business rules and database calls
middleware/ auth.js, errors.js
.env secrets, never committed
package.jsonKeep database queries and business rules out of route files. A route reads the request, calls a service and sends the answer, so the rule can be reused and tested on its own.
| Request | Meaning |
|---|---|
GET /api/jobs | List jobs |
GET /api/jobs/12 | One job |
POST /api/jobs | Create a job |
PATCH /api/jobs/12 | Change some fields |
DELETE /api/jobs/12 | Remove it |