Express TypeScript Boilerplate

repository·master·Indexed 22 days ago

https://github.com/edwinhern/express-typescript

A production-ready Express.js and TypeScript boilerplate for scalable backend web services. It features built-in security with Helmet and CORS, request validation using Zod, observability via pino-http and Swagger UI, and a testing suite configured with Vitest and Supertest. The project implements a feature-based organization and a standardized ServiceResponse pattern for unified API responses.

Tokens
3.6K
Snippets
6
Records
23
Agent score
78%

What's inside express-typescript-boilerplate

  1. Overview of Express TypeScript Boilerplate features

    master

    This boilerplate provides a production-ready foundation for Express.js services with the following integrated tools:

    • Validation & Safety: Request validation and environment configuration using Zod.
    • Security: Built-in protection via Helmet and CORS settings.
    • Observability: Logging with pino-http and interactive API documentation via Swagger UI.
    • Testing: Testing suite pre-configured with Vitest and Supertest.
    • Code Quality: Consistent styling with Biomejs and clean imports via path shortcuts.
    • Deployment: Ready for containerization with an included Dockerfile.
    • Standardization: Unified API responses using the ServiceResponse pattern.
  2. Understand the project folder structure

    master

    The project follows a feature-based organization within the src directory:

    • src/api/: Contains feature modules (e.g., user, healthCheck). Each module typically includes its own router, controller, service, model, repository, and __tests__ folder.
    • src/api-docs/: Handles OpenAPI documentation generation and routing.
    • src/common/: Shared logic across the application:
      • middleware/: Global middleware like errorHandler, rateLimiter, and requestLogger.
      • models/: Shared data structures like serviceResponse.ts.
      • utils/: Helper functions for envConfig, httpHandlers, and commonValidation.
    • src/index.ts & src/server.ts: Application entry points.
    ├── src
    │   ├── api
    │   │   ├── healthCheck
    │   │   └── user
    │   │       ├── userController.ts
    │   │       ├── userModel.ts
    │   │       ├── userRepository.ts
    │   │       ├── userRouter.ts
    │   │       └── userService.ts
    │   ├── api-docs
    │   ├── common
    │   │   ├── middleware
    │   │   ├── models
    │   │   └── utils
    │   ├── index.ts
    │   └── server.ts
  3. Install and set up Express TypeScript Boilerplate

    master

    Follow these steps to clone the repository, install dependencies, and configure your environment:

    1. Clone the repository:
      git clone https://github.com/edwinhern/express-typescript.git
      cd express-typescript
    2. Install dependencies using pnpm:
      pnpm install
    3. Configure environment variables: Copy the template file to create your local .env file, then fill in the required values:
      cp .env.template .env
    git clone https://github.com/edwinhern/express-typescript.git
    cd express-typescript
    pnpm install
    cp .env.template .env
  4. Run the project in development or production modes

    master

    Use the following pnpm commands to run the application depending on your environment:

    • Development Mode: Runs the project with tsx for fast development and error checking.
      pnpm start:dev
    - **Build the project**: Compiles the TypeScript code.
      ```bash
    pnpm build
    • Production Mode: Ensure NODE_ENV="production" is set in your .env file, then build and start the production server.
      pnpm build && pnpm start:prod
    pnpm start:dev
    pnpm build
    pnpm build && pnpm start:prod
  5. Handle graceful shutdown on SIGINT and SIGTERM

    master

    The application implements graceful shutdown logic to handle SIGINT (e.g., Ctrl+C) and SIGTERM signals. When a signal is received, the application:

    1. Logs the shutdown attempt.
    2. Calls server.close() to stop accepting new connections and close existing ones.
    3. Exits the process once the server is closed.
    4. Includes a 10-second safety timeout that forces a process.exit(1) if the server fails to close within that window.
    const onCloseSignal = () => {
    	logger.info("sigint received, shutting down");
    	server.close(() => {
    		logger.info("server closed");
    		process.exit();
    	});
    	setTimeout(() => process.exit(1), 10000).unref(); // Force shutdown after 10s
    };
    
    process.on("SIGINT", onCloseSignal);
    process.on("SIGTERM", onCloseSignal);
  6. Start the Express server

    master

    The application entrypoint initializes the Express server using the configuration provided by the env utility. The server listens on the port specified by env.PORT. Upon successful startup, it logs the environment (NODE_ENV), host (HOST), and port (PORT) using the application's logger.

    // The server is started via the app instance exported from @/server
    const server = app.listen(env.PORT, () => {
    	const { NODE_ENV, HOST, PORT } = env;
    	logger.info(`Server (${NODE_ENV}) running on port http://${HOST}:${PORT}`);
    });
  7. Define User API routes and OpenAPI documentation

    master

    The userRouter.ts file demonstrates how to implement a user-related API module by combining Express routing, Zod schema validation, and OpenAPI documentation registration.

    To implement a new endpoint, you must:

    1. Create an OpenAPIRegistry instance to track documentation.
    2. Register the Express Router.
    3. Register schemas using userRegistry.register(name, schema).
    4. Register paths using userRegistry.registerPath(...) to define the method, path, tags, request parameters, and responses.
    5. Attach the actual logic to the userRouter using Express methods (e.g., .get()), wrapping controllers with validateRequest(schema) to ensure incoming data matches the Zod schema.
    import { OpenAPIRegistry } from "@asteasolutions/zod-to-openapi";
    import express, { type Router } from "express";
    import { z } from "zod";
    import { GetUserSchema, UserSchema } from "@/api/user/userModel";
    import { createApiResponse } from "@/api-docs/openAPIResponseBuilders";
    import { validateRequest } from "@/common/utils/httpHandlers";
    import { userController } from "./userController";
    
    export const userRegistry = new OpenAPIRegistry();
    export const userRouter: Router = express.Router();
    
    // Registering a schema
    userRegistry.register("User", UserSchema);
    
    // Registering a path for documentation
    userRegistry.registerPath({
    	method: "get",
    	path: "/users",
    	tags: ["User"],
    	responses: createApiResponse(z.array(UserSchema), "Success"),
    });
    
    // Attaching the route to Express
    userRouter.get("/", userController.getUsers);
    
    // Registering a path with request parameters
    userRegistry.registerPath({
    	method: "get",
    	path: "/users/{id}",
    	tags: ["User"],
    	request: { params: GetUserSchema.shape.params },
    	responses: createApiResponse(UserSchema, "Success"),
    });
    
    // Attaching a validated route to Express
    userRouter.get("/:id", validateRequest(GetUserSchema), userController.getUser);
  8. Serve OpenAPI documentation with openAPIRouter

    master

    The openAPIRouter is an Express router that provides two endpoints for API documentation:

    1. GET /swagger.json: Returns the raw OpenAPI specification as a JSON object.
    2. GET /: Serves the interactive Swagger UI documentation using swagger-ui-express.

    To use it, import openAPIRouter and mount it to your Express application.

  9. Use userController to handle user requests

    master

    The userController provides Express RequestHandler methods for managing user data. It interacts with the userService to fetch data and returns the service's response status and body directly to the client.

    Available methods:

    • getUsers: Fetches all users.
    • getUser: Fetches a single user by their id passed as a URL parameter.
  10. Create a single OpenAPI response with createApiResponse

    master

    Use createApiResponse to generate an OpenAPI-compliant response object for a single status code. This helper wraps a provided Zod schema using ServiceResponseSchema to ensure the response follows the standard service response format.

    Parameters:

    • schema: A z.ZodTypeAny representing the data structure of the response body.
    • description: A string describing the response.
    • statusCode: The HTTP status code (defaults to StatusCodes.OK).
  11. Validate requests using validateRequest middleware

    master

    The validateRequest middleware uses a Zod schema to validate the body, query, and params of an incoming Express request.

    If validation succeeds, it calls next() to proceed to the next middleware or controller.

    If validation fails, it catches the ZodError and returns a 400 Bad Request response using the ServiceResponse.failure format. The error message is automatically formatted to include the field path (e.g., user.email: Invalid email) and the total number of errors found.