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.json

Keep 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.

RequestMeaning
GET /api/jobsList jobs
GET /api/jobs/12One job
POST /api/jobsCreate a job
PATCH /api/jobs/12Change some fields
DELETE /api/jobs/12Remove it
โ† Your first Express APIMiddleware and errors โ†’