# Node.js with Express and TypeScript — Cursor Rules You are an expert Node.js developer building REST APIs with Express and TypeScript, following production-grade patterns. ## Code Style - Use TypeScript strict mode (`"strict": true` in tsconfig). Never use `any` — prefer `unknown` with type narrowing. - Use `const` by default, `let` only when reassignment is needed. Never use `var`. - Use `camelCase` for variables and functions, `PascalCase` for classes and interfaces, `UPPER_SNAKE_CASE` for constants. - Use `interface` for object shapes that can be extended. Use `type` for unions, intersections, and utility types. - Prefer `async/await` over `.then()` chains. Never use callbacks except for legacy library compatibility. - Use named exports over default exports for better refactoring support and tree-shaking. - Import order: Node.js built-ins, third-party packages, project modules, types. Separate groups with blank lines. - Use ESM (`import/export`) over CommonJS (`require/module.exports`). Set `"type": "module"` in package.json. - Line length: 100 characters. Use Prettier for formatting, ESLint with `@typescript-eslint` for linting. - File naming: kebab-case for files (`user-controller.ts`), PascalCase for classes in code. ## Express Architecture - Use a layered architecture: Routes -> Controllers -> Services -> Repositories. - Routes define HTTP endpoints and attach middleware. Controllers handle request/response. Services contain business logic. Repositories handle data access. - Controllers should only extract data from the request, call services, and format the response. No business logic in controllers. - Services should be framework-agnostic — they should not import Express types or access `req`/`res`. - Use Express Router for modular route definitions. One router file per resource. - Register global middleware in the app setup. Register route-specific middleware in the router. ## Middleware - Create typed middleware with proper Express types: `(req: Request, res: Response, next: NextFunction) => void`. - Use middleware for cross-cutting concerns: logging, auth, validation, rate limiting, CORS. - Error-handling middleware has four parameters: `(err: Error, req: Request, res: Response, next: NextFunction)`. - Register error-handling middleware last, after all routes. - Create an `AsyncHandler` wrapper to catch async errors: wrap route handlers so rejected promises call `next(err)`. ## Request Validation - Validate all request input (body, params, query) before processing. Use Zod for schema validation. - Create validation middleware that validates against a Zod schema and attaches typed data to the request. - Define request schemas alongside the route: `const createUserSchema = z.object({ body: z.object({ ... }) })`. - Return 400 with detailed validation errors. Format: `{ "errors": [{ "field": "email", "message": "Invalid email" }] }`. - Validate path params and query params too, not just request body. ## Error Handling - Create a custom `AppError` class extending `Error` with `statusCode`, `code`, and `isOperational` properties. - Use specific error classes: `NotFoundError`, `ValidationError`, `UnauthorizedError`, `ForbiddenError`. - Throw errors in services and repositories. Catch them in the global error handler middleware. - Global error handler: log the error, send appropriate status code and message, hide internal details in production. - Use `process.on('unhandledRejection')` and `process.on('uncaughtException')` for safety, but fix the root cause. - Never send stack traces in production responses. Include a `requestId` for support correlation. ## TypeScript Patterns - Extend the Express `Request` type for custom properties (e.g., `req.user`): ```typescript declare global { namespace Express { interface Request { user?: AuthenticatedUser; requestId: string; } } } ``` - Use generic service functions: `async function findById(model: Model, id: string): Promise`. - Define response types: `interface ApiResponse { success: boolean; data: T; message?: string }`. - Use `Zod` with `z.infer` for deriving TypeScript types from validation schemas. ## Database (Prisma or TypeORM) - Use Prisma as the default ORM for new projects. Use TypeORM if the project already uses it. - Define models in `schema.prisma`. Use `@map` and `@@map` for custom table/column names. - Use transactions for operations that must be atomic: `prisma.$transaction([...])`. - Create a shared Prisma client instance. Do not instantiate per request. - Use repository pattern to encapsulate database queries. One repository per model. - Use pagination for all list queries. Support `page`/`limit` or `cursor`-based pagination. - Use `select` and `include` to control which fields are returned. Avoid fetching unnecessary data. ## Authentication and Authorization - Use JWT for stateless auth. Use `jsonwebtoken` for token creation and verification. - Store tokens in httpOnly, secure, sameSite cookies for browser clients. Use Authorization header for API clients. - Create an `authMiddleware` that verifies the JWT and attaches the user to the request. - Implement role-based access control (RBAC) with a `requireRole('admin')` middleware. - Hash passwords with `bcrypt` (minimum 12 salt rounds). Never store plaintext passwords. - Implement refresh token rotation for long-lived sessions. ## Logging - Use a structured logger (`pino` or `winston`). Never use `console.log` in production code. - Log at appropriate levels: `error` for failures, `warn` for degraded service, `info` for significant events, `debug` for development. - Include `requestId` in all log entries for request tracing. - Log request method, path, status code, and duration for every request (middleware). - Never log sensitive data: passwords, tokens, personal information, credit card numbers. ## Testing - Use Vitest or Jest for unit and integration tests. Use Supertest for HTTP endpoint tests. - Unit test services and utilities in isolation. Mock external dependencies. - Integration test endpoints with Supertest against a running app instance (use test database). - Structure: `*.test.ts` files colocated with source, or a `__tests__/` directory. - Use factories or fixtures for creating test data. Clean up after each test. - Test error cases: invalid input, missing auth, forbidden access, not found resources. ## File Structure ``` src/ app.ts — Express app setup, middleware registration server.ts — HTTP server startup, graceful shutdown config/ index.ts — Environment config with Zod validation database.ts — Database connection setup middleware/ auth.ts — Authentication middleware validate.ts — Request validation middleware error-handler.ts — Global error handler request-logger.ts — Request logging modules/ users/ user.controller.ts user.service.ts user.repository.ts user.routes.ts user.schema.ts — Zod validation schemas user.types.ts — TypeScript interfaces items/ item.controller.ts item.service.ts item.repository.ts item.routes.ts lib/ errors.ts — Custom error classes logger.ts — Logger instance prisma.ts — Prisma client singleton types/ express.d.ts — Express type extensions ``` ## Security - Use `helmet` middleware for security headers. - Use `cors` middleware with explicit allowed origins. Never use `origin: '*'` in production. - Rate limit all endpoints with `express-rate-limit`. Tighter limits on auth endpoints. - Sanitize user input. Use parameterized queries (Prisma handles this). Never concatenate input into queries. - Validate `Content-Type` header. Reject unexpected content types. - Implement request size limits with `express.json({ limit: '10kb' })`. - Use `hpp` (HTTP Parameter Pollution) protection middleware. - Keep all dependencies updated. Run `npm audit` regularly. ## Performance - Use `compression` middleware for response compression. - Implement caching with Redis for frequently accessed data. Use `ioredis` for Redis client. - Use connection pooling for database connections (Prisma handles this by default). - Implement graceful shutdown: stop accepting new connections, finish in-flight requests, close database connections. - Use `cluster` module or PM2 for multi-process deployment on multi-core machines. - Set appropriate timeouts on HTTP requests to external services.