import { ErrorCode } from "@batch-cooking/shared"; import cookieParser from "cookie-parser"; import cors from "cors"; import express, { type NextFunction, type Request, type Response } from "express"; import { env } from "./config/env.js"; import { authRouter } from "./modules/auth/auth.routes.js"; import { errorHandlerService } from "./services/error-handler.service.js"; /** * Builds a fresh Express application instance (no shared mutable state * between calls — used both by the real server entrypoint and by tests, * which each get their own app via supertest). * * Feature modules are mounted under `/`-prefixed routers as specs land; * `auth` is the first one (login page / profile creation). */ export function createApp() { const app = express(); // Frontend and API run on different origins — `credentials: true` is // required for the httpOnly session cookie to be sent/received. app.use(cors({ origin: env.CORS_ORIGIN, credentials: true })); app.use(express.json()); app.use(cookieParser()); app.get("/health", (_req: Request, res: Response) => { res.status(200).json({ status: "ok" }); }); app.use("/auth", authRouter); // No route matched — same shape as every other error response, via the // shared ErrorCode contract, so clients never special-case 404s. app.use((_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 — this stays a thin adapter. app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => { const { status, body } = errorHandlerService.handle(err); res.status(status).json(body); }); return app; }