Node / Express Guide

Routes

Route file conventions, router factory pattern, and REST method conventions.

Routes wire HTTP paths to controller functions and compose middleware. Each resource has its own route file.

TL;DR — Each route file exports a factory function returning an Express Router. The route index exports useRoutes() mounted as app.use('/api', router()). accessTokenSecret is read from process.env in route files. Auth and role guards are built per-router, not shared globally.

Pattern

Route files export a factory function that returns an Express Router:

// src/routes/auth.route.ts
import express from "express";
import { authenticate } from "@codisolutions23/node-utils";
import { useRevokedTokenRepo } from "../repositories/revoked-token.repo";
import { useAuthController } from "../controllers/auth.controller";
import { useUserController } from "../controllers/user.controller";
import { requireBranchManagerOrAbove } from "../middleware/role-guard.middleware";
import resolveOptionalTenant, {
  resolveRequiredTenant,
} from "../middleware/tenant.middleware";

const router = express.Router();

export default function useAuthRoute() {
  const { existsByJti } = useRevokedTokenRepo();
  const {
    login,
    refreshToken,
    logout,
    getCurrentUser,
    getCurrentUserOrganization,
  } = useAuthController();
  const { createCustomer } = useUserController();

  const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET || "";
  const authMiddleware = authenticate(accessTokenSecret, existsByJti);
  const branchManagerMiddleware = requireBranchManagerOrAbove(
    accessTokenSecret,
    existsByJti,
  );

  // Public routes
  router.post("/login", resolveOptionalTenant, login);
  router.post("/refresh", refreshToken);

  // Registration routes
  router.post("/register/customer", resolveRequiredTenant, createCustomer);

  // Protected routes
  router.get("/user", authMiddleware, getCurrentUser);
  router.get(
    "/organization",
    branchManagerMiddleware,
    getCurrentUserOrganization,
  );
  router.delete("/logout", authMiddleware, logout);

  return router;
}
The router is declared at module scope (not inside the factory). accessTokenSecret is read from process.env directly in route files — this is the current pattern even though src/config.ts exports it as ACCESS_TOKEN_SECRET.

Route Index

All routes are mounted in src/routes/index.ts — the index exports a factory function useRoutes():

// src/routes/index.ts
import express from "express";
import useAuthRoute from "./auth.route";
import useBookingRoute from "./booking.route";
import useOrganizationRoute from "./organization.route";
import useUserRoute from "./user.route";
// ... more route imports

const router = express.Router();

router.get("/v1", (_, res) => {
  res.json({ message: "Welcome to Booking API." });
});

export default function useRoutes() {
  router.use("/auth", useAuthRoute());
  router.use("/bookings", useBookingRoute());
  router.use("/organizations", useOrganizationRoute());
  router.use("/users", useUserRoute());
  // ...

  return router;
}

Mounted in src/app.ts as a function call:

app.use("/api", router()); // router is useRoutes — called with ()

Middleware Composition

Middleware is composed per route — not globally applied unless it truly applies to all routes:

// src/routes/resource.route.ts
export default function useResourceRoute() {
  const { existsByJti } = useRevokedTokenRepo();
  const { getAll, create, update, remove } = useResourceController();

  const accessTokenSecret = process.env.ACCESS_TOKEN_SECRET || "";
  const authMiddleware = authenticate(accessTokenSecret, existsByJti);
  const branchManagerMiddleware = requireBranchManagerOrAbove(
    accessTokenSecret,
    existsByJti,
  );
  const adminMiddleware = requireAdmin(accessTokenSecret, existsByJti);

  router.get("/", authMiddleware, getAll);
  router.post("/", branchManagerMiddleware, create);
  router.patch("/:id", branchManagerMiddleware, update);
  router.delete("/:id", adminMiddleware, remove);

  return router;
}

REST Conventions

OperationMethodPath
ListGET/resources
Get oneGET/resources/:id
CreatePOST/resources
Full replacePUT/resources/:id
Partial updatePATCH/resources/:id
Soft deleteDELETE/resources/:id
Sub-resource actionPATCH/resources/:id/action
Nested resourcePOST/resources/:id/related