import { errorHandlerService } from "@batch-cooking/error-tools"; import { ExpressServer, createErrorMiddleware } from "@batch-cooking/express-tools"; import { ErrorCode } from "@batch-cooking/shared"; import type { Express, Request, Response } from "express"; import { env } from "./config/env.js"; import { authRouter } from "./modules/auth/auth.routes.js"; import { houseRouter } from "./modules/house/house.routes.js"; import { planningRouter } from "./modules/planning/planning.routes.js"; import { preferencesRouter } from "./modules/preferences/preferences.routes.js"; import { profileRouter } from "./modules/profile/profile.routes.js"; import { recipeRouter } from "./modules/recipe/recipe.routes.js"; import { referenceRouter } from "./modules/reference/reference.routes.js"; /** * Builds the API's `ExpressServer`: standard middleware, routes, and the * final error handler, in that order. Returns the `ExpressServer` wrapper * (not just the raw Express app) so `server.ts` can call `.listen()` on * it — {@link createApp} below is the thinner entry point that exposes * just the raw `Express` instance, for test tooling (supertest) that * expects one. */ export function createServer(): ExpressServer { const server = new ExpressServer(); server.setupCore({ corsOrigin: env.CORS_ORIGIN }); server.addRoute("get", "/health", (_req: Request, res: Response) => { res.status(200).json({ status: "ok" }); }); server.mountRouter("/auth", authRouter); server.mountRouter("/house", houseRouter); server.mountRouter("/planning", planningRouter); server.mountRouter("/preferences", preferencesRouter); server.mountRouter("/profile", profileRouter); server.mountRouter("/recipes", recipeRouter); server.mountRouter("/reference", referenceRouter); // Serves the built frontend (production Docker image only — see // FRONTEND_DIST_DIR's doc comment in config/env.ts). Must come after // every API route above (so they always win) and before the catch-all // 404 below (so unmatched GETs fall through to the SPA's index.html // instead of a JSON 404). if (env.FRONTEND_DIST_DIR) { server.serveStaticFrontend(env.FRONTEND_DIST_DIR); } // No route matched — same shape as every other error response, via the // shared ErrorCode contract, so clients never special-case 404s. server.addMiddleware((_req: Request, res: Response) => { res.status(404).json({ code: ErrorCode.NOT_FOUND, message: "Not found" }); }); // Final error-handling middleware: every thrown/`next(err)`-ed error in // the app ends up here. All the "what status/body does this error map // to" logic lives in ErrorHandlerService, from @batch-cooking/error-tools // — this stays a thin adapter. server.setErrorHandler(createErrorMiddleware(errorHandlerService)); return server; } /** * Builds a fresh Express application instance (no shared mutable state * between calls — used both indirectly by the real server entrypoint * (`server.ts`, via {@link createServer}) and directly by tests, which * each get their own app via supertest). */ export function createApp(): Express { return createServer().instance; }