This document provides context for AI agents and human developers working on the Pragmatic Node.js API project. It outlines the project's architecture, conventions, and collaboration model.
A pragmatic foundation for building maintainable REST APIs with Node.js, Express, and TypeScript. The project follows a Vertical Slice Architecture, where each feature is self-contained with its own endpoints, services, domain logic, and validation.
- Vertical Slice Architecture: The application is organized by feature (e.g.,
products). Each feature contains all its layers: routes, endpoints, services, DTOs (request/response), mappers, and validation schemas. - Separation of Concerns (SoC): A strict separation is maintained between different layers:
- Routing (
...-routes.ts): Defines API endpoints and maps them to endpoint handlers. Acts as a factory that receives dependencies. - Endpoints (
...-endpoint.ts): Handles individual HTTP operations (one file per action). Receives the service via closure injection. - Services (
...-service.ts): Encapsulates business logic and orchestrates domain operations. - Domain (
domain/): Contains entity classes with factory methods and business invariants (e.g.,Product.create()). - DTOs: Request (
...-request.ts) and Response (...-response.ts) classes define the shape of data entering and leaving the API. - Mappers (
...-mapper.ts): Transform domain entities into response DTOs. - Schemas (
...-schema.ts): Zod schemas for request validation, colocated with their endpoint. - Data Access Layer (Future): Repository interfaces and persistence adapters will be added in a future iteration.
- Routing (
- Dependency Injection: The
Serverclass, routers, and endpoints receive dependencies (services) via constructor or closure injection, promoting loose coupling and testability. - Configuration Management: A centralized, type-safe configuration module (
src/config) manages environment variables usingdotenv, providing default values and preventing common runtime errors. - Global Error Handling: A centralized
ExceptionHandlerMiddlewarecatches all errors. Custom exception types (e.g.,ValidationException) allow structured error responses. - Schema Validation: Request validation is performed using Zod schemas through a shared
validateRequestWithSchemautility, which throwsValidationExceptionon failure.
- Runtime: Node.js
- Framework: Express.js v5
- Language: TypeScript
- Module System: ESM (
"type": "module"inpackage.json). Relative imports must carry the.jsextension (e.g.import { appRoutes } from "./routes.js"), even though the source file is.ts. - Validation: Zod v4
- Env Management: dotenv
- Transpiler/Runner:
tsxfor development,tscfor production builds. - Linter/Formatter: BiomeJS for code quality and consistent styling.
- Package Manager:
pnpm(exclusively). The version is pinned via thepackageManagerfield inpackage.json.
The project follows a vertical-slice, feature-oriented directory structure.
src/
├── app.ts # Main application entry point
├── server.ts # Core Server class (Express wrapper)
├── routes.ts # Main application router & middleware composition
│
├── config/
│ └── config.ts # Environment variable management
│
├── domain/
│ └── product.entity.ts # Product domain entity with factory method
│
├── exceptions/
│ ├── validation-error.ts # ValidationError interface
│ └── validation-exception.ts # ValidationException class
│
├── middlewares/
│ └── exception-handler.middleware.ts # Global error handling middleware
│
├── shared/
│ └── validations/
│ └── validate-request-with-schema.ts # Zod schema validation utility
│
└── features/
└── products/ # Products feature module
├── products.routes.ts # Products router factory
├── products.service.ts # Products business logic service
│
├── create-product/ # Create Product endpoint (vertical slice)
│ ├── create-product.endpoint.ts
│ ├── create-product.mapper.ts
│ ├── create-product.request.ts
│ ├── create-product.response.ts
│ └── create-products.schema.ts
│
└── get-products/ # Get Products endpoint (vertical slice)
├── get-products.endpoint.ts
├── get-products.mapper.ts
└── get-products.response.ts
Package manager: this project uses pnpm exclusively. Never run npm or yarn commands here — they would generate a competing lockfile. Use pnpm add / pnpm add -D to install packages, pnpm dlx instead of npx, and pnpm install --frozen-lockfile in CI. Only pnpm-lock.yaml is committed. Dependency build scripts are governed by pnpm-workspace.yaml (allowBuilds), since pnpm does not run them by default. A preinstall guard (only-allow pnpm) enforces this automatically and aborts any npm/yarn install. That guard is the one deliberate exception to the pnpm dlx rule: it is invoked as npx only-allow pnpm on purpose, because it must run under whichever package manager the user actually typed. Do not rewrite it to pnpm dlx.
Environment: runtime configuration comes from a .env file, created from .env.template (PORT, NODE_ENV, DEBUG). Every variable has a default and is parsed/validated in src/config/config.ts; .env is git-ignored and must never be committed.
Key scripts are defined in package.json:
pnpm dev: Starts the application in development mode with hot-reloading usingtsx.pnpm build: Compiles the TypeScript code into JavaScript in thedistdirectory.pnpm start: Builds and starts the production version of the application.pnpm biome:lint: Lints the codebase for errors.pnpm biome:lint:fix: Lints and fixes the codebase.pnpm biome:format: Formats the codebase.pnpm biome:format:fix: Formats and writes the changes to the codebase.
- Routing: All API routes are prefixed with
/api. Feature-based routes are nested (e.g.,/api/products). A/healthendpoint is available at the root. - Endpoint Pattern: Each API action has its own directory containing the endpoint handler, DTOs, mapper, and schema (one action per file).
- Naming Conventions:
- Files:
kebab-case.ts - Classes:
PascalCase - Functions/Variables:
camelCase
- Files:
- Imports: Relative imports must end in
.js(ESM requirement), and import ordering is handled automatically by Biome (organizeImports).- Endpoint directories:
verb-noun/(e.g.,create-product/,get-products/)
- Endpoint directories:
- Responses: Endpoint handlers return JSON responses with appropriate HTTP status codes (e.g.,
200for GET,201for POST). Error responses follow a structured format withcode,message, anderrorsfields. - Validation: All incoming requests are validated against Zod schemas before reaching business logic. Validation errors return
400with structured error details.
Directive: The LLM agent for this project acts as an architectural consultant and pair programmer, not as an automated code modifier. The primary goal of the interaction is to discuss architecture, design patterns, and best practices to arrive at a well-reasoned solution, which the user will then implement manually.
Rules of Engagement:
- Source Code Modification: The LLM must not use tools (
write_file,replace) to modify source code files (e.g.,.ts,.json,.sql). All suggestions for code changes must be provided as text or code snippets in the chat response. - Documentation File Modification: The LLM may only use file modification tools (
write_file,replace) for documentation and context files (e.g.,AGENTS.md,README.md), and only when explicitly instructed to do so by the user. - Shell Commands: The LLM should avoid running shell commands (
run_shell_command) unless explicitly asked, especially for commands that modify the file system or git state.