### Run Project in Development Mode Source: https://github.com/edwinhern/express-typescript/blob/master/README.md Start the project in development mode for quick iteration. Ensure environment variables are configured in a `.env` file. ```bash pnpm start:dev ``` -------------------------------- ### Clone and Install Project Dependencies Source: https://github.com/edwinhern/express-typescript/blob/master/README.md Clone the repository and install project dependencies using pnpm. ```bash git clone https://github.com/edwinhern/express-typescript.git cd express-typescript pnpm install ``` -------------------------------- ### Get User by ID Endpoint (Bash) Source: https://context7.com/edwinhern/express-typescript/llms.txt Examples of cURL requests to fetch a user by ID, including valid IDs, non-existent IDs, and invalid (non-numeric) IDs. ```bash # Valid ID curl -s http://localhost:8080/users/1 | jq # { # "success": true, # "message": "User found", # "responseObject": { "id": 1, "name": "Alice", "email": "alice@example.com", "age": 42, "createdAt": "...", "updatedAt": "..." }, # "statusCode": 200 # } # Non-existent ID curl -s http://localhost:8080/users/9999 | jq # { "success": false, "message": "User not found", "responseObject": null, "statusCode": 404 } # Non-numeric ID curl -s http://localhost:8080/users/abc | jq # { "success": false, "message": "Invalid input: params.id: ID must be a numeric value", "responseObject": null, "statusCode": 400 } ``` -------------------------------- ### GET /users Source: https://context7.com/edwinhern/express-typescript/llms.txt Returns all users from the in-memory repository. Responds with 404 when no users exist. Registered in the OpenAPI spec under the "User" tag. ```APIDOC ## GET /users — List All Users Returns all users from the in-memory repository. Responds with `404` when no users exist. Registered in the OpenAPI spec under the "User" tag. ### Request Example ```bash curl -s http://localhost:8080/users | jq # { # "success": true, # "message": "Users found", # "responseObject": [ # { "id": 1, "name": "Alice", "email": "alice@example.com", "age": 42, "createdAt": "...", "updatedAt": "..." }, # { "id": 2, "name": "Robert", "email": "Robert@example.com", "age": 21, "createdAt": "...", "updatedAt": "..." } # ], # "statusCode": 200 # } # When no users exist: # { "success": false, "message": "No Users found", "responseObject": null, "statusCode": 404 } ``` ``` -------------------------------- ### GET /health-check Source: https://context7.com/edwinhern/express-typescript/llms.txt Returns a simple liveness signal confirming the service is running. Registered in the OpenAPI spec under the "Health Check" tag. ```APIDOC ## GET /health-check — Health Check Endpoint Returns a simple liveness signal confirming the service is running. Registered in the OpenAPI spec under the "Health Check" tag. ### Request Example ```bash curl -s http://localhost:8080/health-check | jq # { # "success": true, # "message": "Service is healthy", # "responseObject": null, # "statusCode": 200 # } ``` ### Integration Test Example ```typescript // Integration test pattern (Vitest + Supertest) import request from "supertest"; import { app } from "@/server"; it("GET /health-check returns 200", async () => { const response = await request(app).get("/health-check"); expect(response.statusCode).toBe(200); expect(response.body.success).toBe(true); expect(response.body.message).toBe("Service is healthy"); expect(response.body.responseObject).toBeNull(); }); ``` ``` -------------------------------- ### GET /users/:id Source: https://context7.com/edwinhern/express-typescript/llms.txt Returns a single user matching the provided numeric ID. The :id param is validated by validateRequest(GetUserSchema) — it must be a numeric string representing a positive integer. Returns 400 for invalid formats and 404 for IDs that don't match any record. ```APIDOC ## GET /users/:id — Get User by ID Returns a single user matching the provided numeric ID. The `:id` param is validated by `validateRequest(GetUserSchema)` — it must be a numeric string representing a positive integer. Returns `400` for invalid formats and `404` for IDs that don't match any record. ### Request Example ```bash # Valid ID curl -s http://localhost:8080/users/1 | jq # { # "success": true, # "message": "User found", # "responseObject": { "id": 1, "name": "Alice", "email": "alice@example.com", "age": 42, "createdAt": "...", "updatedAt": "..." }, # "statusCode": 200 # } # Non-existent ID curl -s http://localhost:8080/users/9999 | jq # { "success": false, "message": "User not found", "responseObject": null, "statusCode": 404 } # Non-numeric ID curl -s http://localhost:8080/users/abc | jq # { "success": false, "message": "Invalid input: params.id: ID must be a numeric value", "responseObject": null, "statusCode": 400 } ``` ``` -------------------------------- ### User Model and Zod Schema Validation Source: https://context7.com/edwinhern/express-typescript/llms.txt Defines the User TypeScript type and its Zod schema for data validation. Includes request parameter validation example. ```typescript import { UserSchema, GetUserSchema, type User } from "@/api/user/userModel"; // TypeScript type inferred from Zod: // type User = { id: number; name: string; email: string; age: number; createdAt: Date; updatedAt: Date; } // Validate raw data const result = UserSchema.safeParse({ id: 1, name: "Alice", email: "alice@example.com", age: 42, createdAt: new Date(), updatedAt: new Date(), }); result.success; // true result.data; // User // Route-param validation schema used with validateRequest() const paramResult = GetUserSchema.safeParse({ params: { id: "42" } }); paramResult.success; // true paramResult.data.params.id; // 42 (number – automatically transformed by commonValidations.id) const badParam = GetUserSchema.safeParse({ params: { id: "-5" } }); badParam.success; // false → "ID must be a positive number" ``` -------------------------------- ### Build and Run Project in Production Mode Source: https://github.com/edwinhern/express-typescript/blob/master/README.md Build the project for production and then run it. Requires setting NODE_ENV to "production" in the `.env` file. ```bash pnpm build && pnpm start:prod ``` -------------------------------- ### Docker Build and Run Source: https://context7.com/edwinhern/express-typescript/llms.txt Builds the Docker image for the application and runs the container with specified environment variables for production. ```bash # Build the image docker build -t express-ts-app . # Run the container docker run -p 8080:8080 \ -e NODE_ENV=production \ -e PORT=8080 \ -e HOST=0.0.0.0 \ -e CORS_ORIGIN=https://yourdomain.com \ -e COMMON_RATE_LIMIT_MAX_REQUESTS=100 \ -e COMMON_RATE_LIMIT_WINDOW_MS=1000 \ express-ts-app # docker-compose example # services: # api: # build: . # ports: ["8080:8080"] # environment: # NODE_ENV: production # PORT: 8080 # HOST: 0.0.0.0 # CORS_ORIGIN: https://yourdomain.com ``` -------------------------------- ### Mock UserService with UserRepository Source: https://context7.com/edwinhern/express-typescript/llms.txt Demonstrates unit testing a UserService by injecting a mocked UserRepository. Covers success, empty, not found, and repository error scenarios. ```typescript import { UserRepository } from "@/api/user/userRepository"; import { UserService } from "@/api/user/userService"; import { vi } from "vitest"; // Unit test with a mocked repository vi.mock("@/api/user/userRepository"); const repo = new UserRepository(); const service = new UserService(repo); // inject mock // findAll() – success path (repo.findAllAsync as Mock).mockResolvedValue([{ id: 1, name: "Alice", email: "alice@example.com", age: 42, createdAt: new Date(), updatedAt: new Date() }]); const all = await service.findAll(); // all.success === true, all.statusCode === 200, all.responseObject.length === 1 // findAll() – empty (repo.findAllAsync as Mock).mockResolvedValue(null); const empty = await service.findAll(); // empty.success === false, empty.statusCode === 404, empty.message === "No Users found" // findById() – not found (repo.findByIdAsync as Mock).mockResolvedValue(null); const missing = await service.findById(99); // missing.success === false, missing.statusCode === 404, missing.message === "User not found" // findById() – repository throws (repo.findByIdAsync as Mock).mockRejectedValue(new Error("DB error")); const err = await service.findById(1); // err.success === false, err.statusCode === 500, err.message === "An error occurred while finding user." ``` -------------------------------- ### Project Folder Structure Source: https://github.com/edwinhern/express-typescript/blob/master/README.md Overview of the project's directory and file organization. ```tree ├── biome.json ├── Dockerfile ├── LICENSE ├── package.json ├── pnpm-lock.yaml ├── README.md ├── src │ ├── api │ │ ├── healthCheck │ │ │ ├── __tests__ │ │ │ │ └── healthCheckRouter.test.ts │ │ │ └── healthCheckRouter.ts │ │ └── user │ │ ├── __tests__ │ │ │ ├── userRouter.test.ts │ │ │ └── userService.test.ts │ │ ├── userController.ts │ │ ├── userModel.ts │ │ ├── userRepository.ts │ │ ├── userRouter.ts │ │ └── userService.ts │ ├── api-docs │ │ ├── __tests__ │ │ │ └── openAPIRouter.test.ts │ │ ├── openAPIDocumentGenerator.ts │ │ ├── openAPIResponseBuilders.ts │ │ └── openAPIRouter.ts │ ├── common │ │ ├── __tests__ │ │ │ ├── errorHandler.test.ts │ │ │ └── requestLogger.test.ts │ │ ├── middleware │ │ │ ├── errorHandler.ts │ │ │ ├── rateLimiter.ts │ │ │ └── requestLogger.ts │ │ ├── models │ │ │ └── serviceResponse.ts │ │ └── utils │ │ ├── commonValidation.ts │ │ ├── envConfig.ts │ │ └── httpHandlers.ts │ ├── index.ts │ └── server.ts ├── tsconfig.json └── vite.config.mts ``` -------------------------------- ### Express Server Bootstrap and Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Configures the Express application with global middleware, mounts feature routers, and sets up graceful shutdown handlers. Includes security, logging, and rate limiting middleware. ```typescript // src/server.ts (simplified for reference) import cors from "cors"; import express from "express"; import helmet from "helmet"; import { pino } from "pino"; import { healthCheckRouter } from "@/api/healthCheck/healthCheckRouter"; import { userRouter } from "@/api/user/userRouter"; import { openAPIRouter } from "@/api-docs/openAPIRouter"; import errorHandler from "@/common/middleware/errorHandler"; import rateLimiter from "@/common/middleware/rateLimiter"; import requestLogger from "@/common/middleware/requestLogger"; import { env } from "@/common/utils/envConfig"; const app = express(); app.set("trust proxy", true); // Required when running behind a reverse proxy app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cors({ origin: env.CORS_ORIGIN, credentials: true })); app.use(helmet()); // Sets security-related HTTP response headers app.use(rateLimiter); // IP-based rate limiting app.use(requestLogger); // Pino HTTP structured logging app.use("/health-check", healthCheckRouter); app.use("/users", userRouter); app.use(openAPIRouter); // Swagger UI at "/" and raw JSON at "/swagger.json" app.use(errorHandler()); // 404 catch-all + error logger (must be last) export { app, logger }; ``` ```bash // src/index.ts – starting the server // pnpm start:dev → NODE_ENV=development, live-reload via tsx --watch // pnpm start:prod → NODE_ENV=production, runs compiled dist/index.js // Output: Server (development) running on port http://localhost:8080 ``` -------------------------------- ### List All Users Endpoint (Bash) Source: https://context7.com/edwinhern/express-typescript/llms.txt cURL commands to retrieve all users or handle the case where no users exist. ```bash curl -s http://localhost:8080/users | jq # { # "success": true, # "message": "Users found", # "responseObject": [ # { "id": 1, "name": "Alice", "email": "alice@example.com", "age": 42, "createdAt": "...", "updatedAt": "..." }, # { "id": 2, "name": "Robert", "email": "Robert@example.com", "age": 21, "createdAt": "...", "updatedAt": "..." } # ], # "statusCode": 200 # } # When no users exist: # { "success": false, "message": "No Users found", "responseObject": null, "statusCode": 404 } ``` -------------------------------- ### In-Memory UserRepository Implementation Source: https://context7.com/edwinhern/express-typescript/llms.txt Provides an in-memory implementation for user data access. This can be replaced with real database calls. ```typescript import { UserRepository } from "@/api/user/userRepository"; const repo = new UserRepository(); // Returns all users const all = await repo.findAllAsync(); // [{ id: 1, name: "Alice", ... }, { id: 2, name: "Robert", ... }] // Returns matching user or null const user = await repo.findByIdAsync(1); // { id: 1, name: "Alice", email: "alice@example.com", age: 42, createdAt: Date, updatedAt: Date } const missing = await repo.findByIdAsync(999); // null // Swapping in a real database (example with a hypothetical ORM): // export class UserRepository { // async findAllAsync(): Promise { // return db.user.findMany(); // } // async findByIdAsync(id: number): Promise { // return db.user.findUnique({ where: { id } }); // } // } ``` -------------------------------- ### Generate OpenAPI Document Programmatically Source: https://context7.com/edwinhern/express-typescript/llms.txt Merges OpenAPI registry instances to generate an OpenAPI 3.0 document. Shows how to register paths for a new feature. ```typescript import { generateOpenAPIDocument } from "@/api-docs/openAPIDocumentGenerator"; const doc = generateOpenAPIDocument(); // doc.openapi === "3.0.0" // doc.info === { version: "1.0.0", title: "Swagger API" } // doc.paths contains /health-check, /users, /users/{id} // Register a new feature's schemas in its own router file: import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi"; import { createApiResponse } from "@/api-docs/openAPIResponseBuilders"; import { z } from "zod"; export const productRegistry = new OpenAPIRegistry(); productRegistry.registerPath({ method: "get", path: "/products", tags: ["Product"], responses: createApiResponse(z.array(ProductSchema), "Success"), }); // Then add productRegistry to the merger in openAPIDocumentGenerator.ts: // const registry = new OpenAPIRegistry([healthCheckRegistry, userRegistry, productRegistry]); ``` -------------------------------- ### Environment Configuration with Zod Source: https://context7.com/edwinhern/express-typescript/llms.txt Parses and validates environment variables at startup using Zod. The process exits if validation fails. Provides convenience boolean flags for environment types. ```dotenv # .env (copy from .env.template) NODE_ENV="development" // 'development' | 'production' | 'test' (default: 'production') PORT="8080" // Server port (default: 8080) HOST="localhost" // Server host (default: 'localhost') CORS_ORIGIN="http://localhost:8080" // Allowed CORS origin (must be a valid URL) COMMON_RATE_LIMIT_MAX_REQUESTS="20" // Max requests per window per IP (default: 1000) COMMON_RATE_LIMIT_WINDOW_MS="1000" // Rate-limit window in ms (default: 1000) ``` ```typescript // Usage in application code import { env } from "@/common/utils/envConfig"; console.log(env.PORT); // 8080 (number, coerced) console.log(env.NODE_ENV); // 'development' console.log(env.isDevelopment); // true console.log(env.isProduction); // false console.log(env.isTest); // false // Invalid env var example – process will throw at startup: // ❌ Invalid environment variables: { PORT: [ { message: 'Expected number, received nan' } ] } ``` -------------------------------- ### Implement IP-Based Rate Limiting Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Configure an IP-based rate limiter using `express-rate-limit`, driven by environment variables for window size and request cap. Emits standard and legacy rate limit headers. ```typescript // Configuration (via .env) COMMON_RATE_LIMIT_WINDOW_MS="1000" // 1 second base unit; actual window = 15 * 60 * this value COMMON_RATE_LIMIT_MAX_REQUESTS="20" // 20 requests per window per IP // The middleware is applied globally in server.ts: // app.use(rateLimiter); // When the limit is exceeded the client receives: // HTTP 429 Too Many Requests // Body: "Too many requests, please try again later." // Headers: // X-RateLimit-Limit: 20 // X-RateLimit-Remaining: 0 // RateLimit-Reset: // To use a different limiter on a specific router: import { rateLimit } from "express-rate-limit"; const strictLimiter = rateLimit({ windowMs: 60_000, limit: 5 }); router.post("/login", strictLimiter, loginHandler); ``` -------------------------------- ### Interact with OpenAPI Spec Source: https://context7.com/edwinhern/express-typescript/llms.txt Mounts Swagger UI at the root path and exposes the raw OpenAPI JSON document. Use curl to download the spec and jq to parse it. ```bash # Open interactive API browser in a browser open http://localhost:8080/ # Download raw OpenAPI spec curl -s http://localhost:8080/swagger.json | jq .info # { "version": "1.0.0", "title": "Swagger API" } curl -s http://localhost:8080/swagger.json | jq '.paths | keys' # [ "/health-check", "/users", "/users/{id}" ] ``` -------------------------------- ### Unit Test with Vitest Mocks Source: https://context7.com/edwinhern/express-typescript/llms.txt Conducts unit tests on the service layer by mocking the repository using Vitest's mocking capabilities. ```typescript // Unit test (service layer) – mock the repository import { vi } from "vitest"; vi.mock("@/api/user/userRepository"); const repo = new UserRepository(); const service = new UserService(repo); (repo.findByIdAsync as Mock).mockResolvedValue(null); const result = await service.findById(1); expect(result.statusCode).toBe(404); ``` -------------------------------- ### Rate Limiter Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt An IP-based rate limiter middleware for Express, configured via environment variables. It supports both legacy and standard rate limit headers and returns a 429 Too Many Requests response when limits are exceeded. ```APIDOC ## Rate Limiter Middleware ### Description IP-based rate limiter powered by `express-rate-limit`. Window size and request cap are driven by the validated `env` config. Both legacy (`X-RateLimit-*`) and standard (`RateLimit-*`) headers are emitted. ### Configuration (via .env) ``` COMMON_RATE_LIMIT_WINDOW_MS="1000" // 1 second base unit; actual window = 15 * 60 * this value COMMON_RATE_LIMIT_MAX_REQUESTS="20" // 20 requests per window per IP ``` ### Usage Applied globally in `server.ts`: ```typescript // The middleware is applied globally in server.ts: // app.use(rateLimiter); ``` ### Response when Limit Exceeded When the limit is exceeded, the client receives: - **HTTP Status**: 429 Too Many Requests - **Body**: "Too many requests, please try again later." - **Headers**: - `X-RateLimit-Limit`: 20 - `X-RateLimit-Remaining`: 0 - `RateLimit-Reset`: ### Applying to Specific Routers To use a different limiter on a specific router: ```typescript import { rateLimit } from "express-rate-limit"; const strictLimiter = rateLimit({ windowMs: 60_000, limit: 5 }); router.post("/login", strictLimiter, loginHandler); ``` ``` -------------------------------- ### Construct ServiceResponse Instances Source: https://context7.com/edwinhern/express-typescript/llms.txt Use static factory methods to create success or failure responses. The `ServiceResponseSchema` Zod schema can generate OpenAPI-compatible schemas. ```typescript import { ServiceResponse, ServiceResponseSchema } from "@/common/models/serviceResponse"; import { StatusCodes } from "http-status-codes"; import { z } from "zod"; // --- Success response --- const ok = ServiceResponse.success("Users found", [{ id: 1, name: "Alice" }]); // { success: true, message: "Users found", responseObject: [{...}], statusCode: 200 } // --- Failure response with custom status --- const notFound = ServiceResponse.failure("User not found", null, StatusCodes.NOT_FOUND); // { success: false, message: "User not found", responseObject: null, statusCode: 404 } // --- In an Express route handler --- userRouter.get("/", async (_req, res) => { const serviceResponse = await userService.findAll(); res.status(serviceResponse.statusCode).send(serviceResponse); }); // --- Generating an OpenAPI-compatible Zod schema for a response --- const UserResponseSchema = ServiceResponseSchema( z.object({ id: z.number(), name: z.string(), email: z.string().email() }) ); // Produces: { success: boolean, message: string, responseObject?: User, statusCode: number } ``` -------------------------------- ### Health Check Endpoint (Bash) Source: https://context7.com/edwinhern/express-typescript/llms.txt A simple cURL command to check the liveness signal of the service. ```bash curl -s http://localhost:8080/health-check | jq # { # "success": true, # "message": "Service is healthy", # "responseObject": null, # "statusCode": 200 # } ``` -------------------------------- ### Error Handler Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Returns a tuple [RequestHandler, ErrorRequestHandler] that provides a 404 catch-all for unmatched routes and an error propagation handler that records the error on res.locals before passing it along. ```APIDOC ## Error Handler Middleware (`src/common/middleware/errorHandler.ts`) Returns a tuple `[RequestHandler, ErrorRequestHandler]` that provides a 404 catch-all for unmatched routes and an error propagation handler that records the error on `res.locals` before passing it along. ### Usage ```typescript import errorHandler from "@/common/middleware/errorHandler"; import express from "express"; const app = express(); // ... mount all routes first ... // Must be registered LAST app.use(errorHandler()); // Any unmatched route: // GET /does-not-exist → 404 "Not Found" // Unhandled thrown error anywhere above: // GET /broken → 500 Internal Server Error // The error is stored in res.locals.err for upstream error-tracking integrations // Testing the handler: import request from "supertest"; const response = await request(app).get("/this-route-does-not-exist"); // response.status === 404 ``` ``` -------------------------------- ### Integration Test with Supertest Source: https://context7.com/edwinhern/express-typescript/llms.txt Performs integration tests on the router layer using Supertest, exercising the full middleware stack without mocking. ```typescript // pnpm test – run all tests once // pnpm test:cov – run with V8 coverage report // Integration test (router layer) – no mocking, exercises full middleware stack import request from "supertest"; import { app } from "@/server"; import { StatusCodes } from "http-status-codes"; describe("GET /users/:id", () => { it("returns 200 for a valid ID", async () => { const response = await request(app).get("/users/1"); expect(response.statusCode).toBe(StatusCodes.OK); expect(response.body.success).toBe(true); expect(response.body.responseObject.name).toBe("Alice"); }); it("returns 400 for a non-numeric ID", async () => { const response = await request(app).get("/users/abc"); expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST); expect(response.body.success).toBe(false); }); }); ``` -------------------------------- ### Request Logger Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Exports an array of three middleware functions that together provide request-ID propagation, pino-http structured logging, and response-body capture. Log level is automatically derived from the response status code. ```APIDOC ## Request Logger Middleware (`src/common/middleware/requestLogger.ts`) Exports an array of three middleware functions that together provide request-ID propagation, pino-http structured logging, and (in non-production environments) response-body capture. Log level is automatically derived from the response status code. ### Usage ```typescript // Applied globally in server.ts: // app.use(requestLogger); // [addRequestId, captureResponseBody, httpLogger] // Pass or receive a correlation ID via header: // curl -H "X-Request-Id: my-trace-123" http://localhost:8080/users // → Response header: X-Request-Id: my-trace-123 // → If no header provided, a UUID is generated automatically // Development log output (pino-pretty): // [12:00:01.234] INFO: GET /users completed // req: { method: "GET", url: "/users", id: "550e8400-e29b-41d4-a716-446655440000" } // res: { statusCode: 200 } // Log levels by status code: // 2xx / 3xx → info // 4xx → warn // 5xx → error // In production, pino-pretty transport is disabled; logs are emitted as raw JSON: // {"level":30,"time":1700000000000,"req":{"method":"GET","url":"/users","id":"..."},"res":{"statusCode":200},"msg":"GET /users completed"} ``` ``` -------------------------------- ### Create OpenAPI Response Config Source: https://context7.com/edwinhern/express-typescript/llms.txt Generates an OpenAPI response-config object. Wraps the data schema inside ServiceResponseSchema to reflect the actual response envelope. ```typescript import { createApiResponse } from "@/api-docs/openAPIResponseBuilders"; import { z } from "zod"; import { StatusCodes } from "http-status-codes"; const ProductSchema = z.object({ id: z.number(), name: z.string(), price: z.number() }); // Single 200 success response const responses = createApiResponse(ProductSchema, "Product retrieved successfully"); // { // 200: { // description: "Product retrieved successfully", // content: { "application/json": { schema: ServiceResponseSchema(ProductSchema) } } // } // } // Custom status code const createdResponse = createApiResponse(ProductSchema, "Product created", StatusCodes.CREATED); // { 201: { ... } } // Used when registering a path: registry.registerPath({ method: "post", path: "/products", tags: ["Product"], responses: createApiResponse(ProductSchema, "Created", StatusCodes.CREATED), }); ``` -------------------------------- ### Error Handler Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Provides a 404 catch-all for unmatched routes and an error propagation handler. Errors are recorded on res.locals before being passed along. ```typescript import errorHandler from "@/common/middleware/errorHandler"; import express from "express"; const app = express(); // ... mount all routes first ... // Must be registered LAST app.use(errorHandler()); // Any unmatched route: // GET /does-not-exist → 404 "Not Found" // Unhandled thrown error anywhere above: // GET /broken → 500 Internal Server Error // The error is stored in res.locals.err for upstream error-tracking integrations // Testing the handler: import request from "supertest"; const response = await request(app).get("/this-route-does-not-exist"); // response.status === 404 ``` -------------------------------- ### ServiceResponse Class Source: https://context7.com/edwinhern/express-typescript/llms.txt A generic response envelope for API endpoints. Use static factory methods `success` and `failure` to create instances. `ServiceResponseSchema` can generate OpenAPI response schemas. ```APIDOC ## `ServiceResponse` Class ### Description A generic response envelope used by every API endpoint. Static factory methods `success` and `failure` construct instances; the exported `ServiceResponseSchema` Zod factory generates the matching OpenAPI response schema for any data type. ### Usage Examples ```typescript import { ServiceResponse, ServiceResponseSchema } from "@/common/models/serviceResponse"; import { StatusCodes } from "http-status-codes"; import { z } from "zod"; // --- Success response --- const ok = ServiceResponse.success("Users found", [{ id: 1, name: "Alice" }]); // { success: true, message: "Users found", responseObject: [{...}], statusCode: 200 } // --- Failure response with custom status --- const notFound = ServiceResponse.failure("User not found", null, StatusCodes.NOT_FOUND); // { success: false, message: "User not found", responseObject: null, statusCode: 404 } // --- In an Express route handler --- userRouter.get("/", async (_req, res) => { const serviceResponse = await userService.findAll(); res.status(serviceResponse.statusCode).send(serviceResponse); }); // --- Generating an OpenAPI-compatible Zod schema for a response --- const UserResponseSchema = ServiceResponseSchema( z.object({ id: z.number(), name: z.string(), email: z.string().email() }) ); // Produces: { success: boolean, message: string, responseObject?: User, statusCode: number } ``` ``` -------------------------------- ### validateRequest Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Express middleware factory that validates request bodies, queries, and parameters against a Zod schema. It short-circuits requests with a 400 Bad Request on validation failure, providing details of the errors. ```APIDOC ## `validateRequest` Middleware ### Description Express middleware factory that validates `req.body`, `req.query`, and `req.params` against a Zod schema. On failure it short-circuits the request with a `400 Bad Request` containing a `ServiceResponse.failure` listing every validation error. ### Usage Example ```typescript import { validateRequest } from "@/common/utils/httpHandlers"; import { z } from "zod"; import express from "express"; const router = express.Router(); // Define a schema that covers params / query / body const CreateUserSchema = z.object({ body: z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().positive(), }), }); router.post("/users", validateRequest(CreateUserSchema), (req, res) => { // Only reached when body is valid res.json({ created: req.body }); }); // Example of a failed request: // POST /users body: { "name": "", "email": "bad", "age": -1 } // → 400 Bad Request // { // "success": false, // "message": "Invalid input (3 errors): body.name: String must contain at least 1 character(s); body.email: Invalid email; body.age: Number must be greater than 0", // "responseObject": null, // "statusCode": 400 // } ``` ``` -------------------------------- ### Request Logger Middleware Source: https://context7.com/edwinhern/express-typescript/llms.txt Exports middleware for request ID propagation, structured logging with pino-http, and response body capture. Log level is derived from the response status code. ```typescript // Applied globally in server.ts: // app.use(requestLogger); // [addRequestId, captureResponseBody, httpLogger] // Pass or receive a correlation ID via header: // curl -H "X-Request-Id: my-trace-123" http://localhost:8080/users // → Response header: X-Request-Id: my-trace-123 // → If no header provided, a UUID is generated automatically // Development log output (pino-pretty): // [12:00:01.234] INFO: GET /users completed // req: { method: "GET", url: "/users", id: "550e8400-e29b-41d4-a716-446655440000" } // res: { statusCode: 200 } // Log levels by status code: // 2xx / 3xx → info // 4xx → warn // 5xx → error // In production, pino-pretty transport is disabled; logs are emitted as raw JSON: // {"level":30,"time":1700000000000,"req":{"method":"GET","url":"/users","id":"..."},"res":{"statusCode":200},"msg":"GET /users completed"} ``` -------------------------------- ### commonValidations Source: https://context7.com/edwinhern/express-typescript/llms.txt Provides shared Zod validators for common field types, such as an `id` validator that transforms a string to a positive number, useful for route parameters. ```APIDOC ## `commonValidations` ### Description Shared Zod validators for recurring field types. Currently exposes `id` — a string-to-positive-number transformer used in route params. ### Usage Example ```typescript import { commonValidations } from "@/common/utils/commonValidation"; import { z } from "zod"; // Validate and transform a string route param to a positive number const GetItemSchema = z.object({ params: z.object({ id: commonValidations.id }), }); // Example validations: // "42" → 42 ✓ // "0" → fails: "ID must be a positive number" // "abc" → fails: "ID must be a numeric value" // Extend with your own common validators in the same file: // export const commonValidations = { // id: ..., // slug: z.string().regex(/^[a-z0-9-]+$/), // pagination: z.object({ page: z.coerce.number().default(1), limit: z.coerce.number().default(20) }), // }; ``` ``` -------------------------------- ### Health Check Endpoint (TypeScript Integration Test) Source: https://context7.com/edwinhern/express-typescript/llms.txt An integration test using Vitest and Supertest to verify the health check endpoint returns a 200 status code and expected body. ```typescript // Integration test pattern (Vitest + Supertest) import request from "supertest"; import { app } from "@/server"; it("GET /health-check returns 200", async () => { const response = await request(app).get("/health-check"); expect(response.statusCode).toBe(200); expect(response.body.success).toBe(true); expect(response.body.message).toBe("Service is healthy"); expect(response.body.responseObject).toBeNull(); }); ``` -------------------------------- ### Validate Express Request Body, Query, and Params with Zod Source: https://context7.com/edwinhern/express-typescript/llms.txt The `validateRequest` middleware factory validates incoming request data against a Zod schema. On failure, it returns a `ServiceResponse.failure` with validation errors. ```typescript import { validateRequest } from "@/common/utils/httpHandlers"; import { z } from "zod"; import express from "express"; const router = express.Router(); // Define a schema that covers params / query / body const CreateUserSchema = z.object({ body: z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().int().positive(), }), }); router.post("/users", validateRequest(CreateUserSchema), (req, res) => { // Only reached when body is valid res.json({ created: req.body }); }); // POST /users body: { "name": "", "email": "bad", "age": -1 } // → 400 Bad Request // { // "success": false, // "message": "Invalid input (3 errors): body.name: String must contain at least 1 character(s); body.email: Invalid email; body.age: Number must be greater than 0", // "responseObject": null, // "statusCode": 400 // } ``` -------------------------------- ### Create Common Zod Validators Source: https://context7.com/edwinhern/express-typescript/llms.txt Define reusable Zod validators for common field types, such as transforming string route parameters to positive numbers. ```typescript import { commonValidations } from "@/common/utils/commonValidation"; import { z } from "zod"; // Validate and transform a string route param to a positive number const GetItemSchema = z.object({ params: z.object({ id: commonValidations.id }), }); // "42" → 42 ✓ // "0" → fails: "ID must be a positive number" // "abc" → fails: "ID must be a numeric value" // Extend with your own common validators in the same file: // export const commonValidations = { // id: ..., // slug: z.string().regex(/^[a-z0-9-]+$/), // pagination: z.object({ page: z.coerce.number().default(1), limit: z.coerce.number().default(20) }), // }; ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.