Your Express server works locally but the moment real traffic hits it, the error handling is nonexistent, the database connections leak, and nobody thought about rate limiting. These recipes fix that — each prompt produces production-hardened Node.js code that handles the real-world problems tutorials skip.
API scaffolding prompts with proper middleware, validation, and error handling
Database integration recipes with connection pooling and migrations
Authentication patterns with JWT refresh and RBAC
Testing and deployment recipes for production readiness
Scenario: You are starting a new service and need a project structure that scales beyond 10 routes without becoming unmaintainable.
Tip
Create a Node.js TypeScript project with Express following clean architecture. Structure: src/domain/ for entities and repository interfaces, src/application/ for use cases, src/infrastructure/ for database adapters, external API clients, and Express configuration, src/presentation/ for route handlers, DTOs, and middleware. Include: tsconfig with strict mode and path aliases (@domain, @application, @infrastructure, @presentation), ESLint with TypeScript rules, Vitest for testing, dependency injection using tsyringe with a container setup file. Create a health check endpoint at GET /health that returns { status: 'ok', timestamp, uptime }. Add graceful shutdown handling: listen for SIGTERM and SIGINT, close the HTTP server, drain database connections, then exit. Write the health check test.
Expected output: Project scaffold with 4-layer architecture, DI container, health check, graceful shutdown, and tests.
Scenario: You need a users API with proper input validation, consistent error responses, and typed request/response bodies.
Tip
Create a complete CRUD API for a users resource at /api/v1/users. Use Zod schemas for request validation: createUserSchema (name required 2-100 chars, email required and valid, password min 8 chars with complexity regex, role enum user/admin), updateUserSchema (all fields optional, partial of create). Create a validate(schema) middleware that validates req.body and returns 422 with field-level errors in the format { errors: [{ field: 'email', message: 'Invalid email format' }] }. Create a global error handler middleware that catches: ValidationError (422), NotFoundError (404), ConflictError (409), UnauthorizedError (401), and unknown errors (500 with generic message in production, full stack in development). Every handler should use async/await with a catchAsync wrapper that forwards errors to next(). Response format: { data: T } for success, { error: { code, message, details? } } for errors. Write integration tests using supertest for every endpoint and error case.
Expected output: Route handlers, Zod schemas, validation middleware, error handler, catchAsync utility, and supertest tests.
Scenario: Your API needs authentication that does not force users to re-login every 15 minutes but also does not use long-lived tokens.
Tip
Implement JWT authentication with refresh token rotation. (1) Login endpoint POST /api/auth/login: validate credentials against bcrypt hash, generate a short-lived access token (15 minutes) with userId and role claims, generate a refresh token (30 days) stored as a hashed value in the database with a family ID for rotation detection. Return both tokens. (2) Refresh endpoint POST /api/auth/refresh: validate the refresh token, check it has not been revoked, issue new access + refresh tokens, invalidate the old refresh token. If a revoked refresh token is reused, invalidate ALL tokens in that family (theft detection). (3) Auth middleware: extract Bearer token from Authorization header, verify JWT signature and expiration, attach user to req.user. (4) Logout endpoint POST /api/auth/logout: revoke the refresh token family. (5) Password reset flow: POST /api/auth/forgot-password generates a time-limited token sent via email, POST /api/auth/reset-password validates token and updates password. Test token rotation, theft detection, and expiration handling.
Expected output: Auth routes, JWT utilities, auth middleware, refresh token model, and security tests.
Scenario: Your public API gets hammered by scrapers and you have no protection against abuse.
Tip
Implement multi-layer rate limiting. (1) Global rate limiter using express-rate-limit with Redis store (for multi-instance): 100 requests per minute per IP. Return 429 with Retry-After header and JSON error body. (2) Per-endpoint limits: POST /api/auth/login at 5 attempts per 15 minutes per IP (brute force protection), POST /api/auth/forgot-password at 3 per hour per email, POST /api/*/upload at 10 per hour per user. (3) Sliding window rate limiter for API keys: read the plan from the user record (free: 100/hour, pro: 1000/hour, enterprise: 10000/hour), track usage in Redis sorted sets with millisecond precision, return X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset headers on every response. (4) Create a rateLimiter({ points, duration, keyGenerator }) factory function for reusable limit configurations. Test rate limit enforcement, header values, and Redis key cleanup.
Expected output: Rate limiter factory, Redis store integration, per-endpoint configs, response headers, and tests.
Scenario: Your database queries are scattered everywhere with no connection pooling, no transactions, and migrations run manually.
Tip
Set up Prisma ORM for a PostgreSQL database in this Node.js project. (1) Define the schema in prisma/schema.prisma: User (id uuid, email unique, name, hashedPassword, role, createdAt, updatedAt), Product (id uuid, name, slug unique, description, price Decimal, stock Int, categoryId, createdAt), Order (id uuid, userId, status enum pending/paid/shipped/delivered/cancelled, total Decimal, createdAt), OrderItem (orderId, productId, quantity, unitPrice, composite key). (2) Create a singleton Prisma client in src/infrastructure/database.ts with query logging in development and connection pool configuration (min 2, max 10). (3) Write a transaction pattern for order creation: createOrder(userId, items[]) that validates stock for each item, decrements stock, creates the order and items, and calculates the total — all in a single transaction that rolls back if any item is out of stock. (4) Create a seed script with realistic data using Faker. (5) Add a migration script that generates and applies migrations. Test the transaction rollback behavior when stock is insufficient.
Expected output: Prisma schema, client singleton, transaction patterns, seed script, and transaction tests.
Scenario: Your API processes image uploads synchronously, blocking the response for 30 seconds. You need background processing.
Tip
Set up BullMQ for background job processing. (1) Create a job queue configuration in src/infrastructure/queue.ts with Redis connection, default job options (attempts: 3, backoff exponential), and typed job definitions using TypeScript. (2) Create job processors: processImageUpload (resize to multiple sizes, convert to WebP, upload to S3, update database record), sendEmail (render template, send via Resend, log delivery status), generateReport (query database, generate PDF, store in S3, notify user). (3) Create a queue dashboard at /admin/queues using @bull-board/express for monitoring jobs. (4) Add job scheduling: daily report generation at 2 AM using BullMQ’s repeat option. (5) Implement graceful shutdown: stop accepting new jobs, wait for active jobs to complete (max 30 seconds), then force-close. (6) Add dead letter queue handling: after 3 failures, move to DLQ and send an alert to the ops channel. Test job processing, retry behavior, and failure handling.
Expected output: Queue setup, 3 processors, dashboard route, scheduled jobs, graceful shutdown, and tests.
Scenario: Your chat application polls the server every 2 seconds. You need real-time messaging.
Tip
Implement WebSocket communication using Socket.io with the Express server. (1) Set up Socket.io with CORS configuration, JWT authentication middleware (verify token on connection, reject unauthorized), and Redis adapter for horizontal scaling across multiple server instances. (2) Create room management: users join rooms based on conversation IDs. Create joinRoom(conversationId), leaveRoom(conversationId), and sendMessage(conversationId, content) handlers. (3) Implement typing indicators: emit ‘typing’ when a user starts typing, debounce to stop after 3 seconds of inactivity. (4) Implement presence tracking: maintain an online/offline status per user in Redis, broadcast status changes to relevant rooms. (5) Message delivery acknowledgment: client acknowledges receipt, server marks as delivered, then read. Handle offline message queuing: store messages for offline users and deliver on reconnect. (6) Add reconnection handling with exponential backoff on the client side. Test message delivery, presence updates, and multi-instance broadcasting.
Expected output: Socket.io setup, room handlers, presence system, message acknowledgment, and tests.
Scenario: Your production logs are console.log statements with no structure, no correlation, and no way to trace a request across services.
Tip
Implement structured logging with Pino. (1) Configure Pino with JSON output in production and pretty-print in development. Set log levels per environment (debug in dev, info in production). (2) Create a request logging middleware that assigns a unique requestId (from X-Request-Id header or generated UUID) and logs: method, URL, status code, response time, user ID (if authenticated), and request body (redacted for sensitive fields like password, token, creditCard). (3) Create a logger.child({ requestId }) pattern so every log within a request includes the requestId for correlation. (4) Add sensitive field redaction: define a list of field names (password, token, secret, authorization) that are automatically replaced with ‘[REDACTED]’ in all log output. (5) Create a custom error serializer that includes stack traces in development but omits them in production. (6) Set up log shipping to stdout for container environments (picked up by Datadog/ELK). Test that request IDs propagate through nested function calls and that sensitive fields are redacted.
Expected output: Pino configuration, request middleware, child logger pattern, redaction rules, and tests.
Scenario: Your API is live with paying customers and you need to ship breaking changes without breaking their integrations.
Tip
Implement API versioning with OpenAPI documentation. (1) URL-based versioning: mount v1 routes at /api/v1/ and v2 routes at /api/v2/. Create a router factory that accepts a version and returns configured routes. V1 stays frozen for backwards compatibility. (2) Create an OpenAPI 3.1 spec using swagger-jsdoc: annotate each route handler with JSDoc comments defining parameters, request body schema (reference Zod schemas), response schemas, and error responses. (3) Serve Swagger UI at /api/docs for interactive documentation. (4) Generate TypeScript types from the OpenAPI spec for client SDKs using openapi-typescript. (5) Add a deprecation middleware: when a v1 endpoint has a v2 replacement, add Deprecation and Sunset headers with the sunset date. Log deprecation warnings. (6) Create a migration guide document listing every v1-to-v2 change. Test that v1 endpoints still work unchanged and v2 endpoints serve new behavior.
Expected output: Versioned router, OpenAPI annotations, Swagger UI setup, deprecation middleware, and tests.
Scenario: Your file upload endpoint loads the entire file into memory, crashes on files over 100 MB, and accepts any file type.
Tip
Implement production-grade file uploads with multer and streaming. (1) Configure multer with disk storage for temporary files, file size limit (50 MB), file type validation (accept only images and PDFs by checking both MIME type and magic bytes, not just extension). (2) Create a streaming upload to S3: pipe the file stream directly to S3 using the AWS SDK v3 multipart upload, never loading the full file into memory. (3) Image processing pipeline: for image uploads, use sharp to generate thumbnails (150x150), medium (800x600), and original size. Convert all to WebP. Upload all variants to S3 with proper Content-Type headers. (4) Return a response with URLs for each variant. Store the file metadata in the database (original name, size, mime type, S3 keys, upload date). (5) Add virus scanning using ClamAV via a ClamScan integration before processing. (6) Cleanup: delete the temporary file after processing, add a scheduled job that cleans orphaned S3 objects not referenced in the database. Test upload with valid/invalid files, oversize files, and virus-infected test files.
Expected output: Upload route, S3 streaming, image processing, virus scanning, cleanup job, and tests.
Scenario: Your database gets crushed by repeated identical queries. The product listing page makes the same query 10,000 times per minute.
Tip
Implement a caching layer using Redis. (1) Create a CacheService class with typed methods: get<T>(key), set<T>(key, value, ttl), delete(key), deletePattern(pattern). Handle Redis connection failures gracefully — if Redis is down, bypass cache and hit the database directly (circuit breaker pattern). (2) Create a cacheMiddleware(options) Express middleware: check cache for GET requests, return cached response if fresh, otherwise proceed to handler and cache the response. Use ETags for conditional requests. (3) Implement cache invalidation: when a product is updated, delete the product detail cache AND the listing cache. Use cache tags: tag each cache entry with resource type, invalidate by tag. (4) Add cache warming: on server start, preload the top 100 most-accessed resources. (5) Implement stale-while-revalidate: serve stale cached data immediately while fetching fresh data in the background. (6) Monitor cache hit rate and size via a /admin/cache/stats endpoint. Test cache hits, misses, invalidation, and Redis failure fallback.
Expected output: CacheService, middleware, invalidation logic, warming script, monitoring endpoint, and tests.
Scenario: Your Node.js app deploys via SSH and manual npm install on the server. Time to containerize and automate.
Tip
Create a production deployment pipeline. (1) Multi-stage Dockerfile: build stage installs dependencies and compiles TypeScript, production stage copies only compiled JS and production node_modules, uses node:20-alpine, runs as non-root user, adds healthcheck, uses dumb-init for signal handling. Final image under 150 MB. (2) docker-compose.yml for local development: app with hot reload (volume mount src/), PostgreSQL, Redis, Mailhog for email testing. docker-compose.prod.yml with proper resource limits. (3) GitHub Actions workflow: on PR — lint, type-check, test with PostgreSQL service container. On merge to main — build Docker image, push to GitHub Container Registry, deploy to staging with health check verification, manual approval for production deploy. (4) Database migration step: run prisma migrate deploy before starting the new container. (5) Rollback strategy: if the health check fails after deploy, revert to the previous image tag. Test the Docker build and health check endpoint.
Expected output: Dockerfile, compose files, GitHub Actions workflow, migration script, and rollback strategy.
Caution
Common Node.js pitfalls:
Unhandled promise rejections: If the AI forgets to wrap async route handlers, unhandled rejections crash the process in Node 20+. Always use the catchAsync wrapper or express-async-errors.
Memory leaks in closures: Watch for event listeners or intervals created in middleware that are never cleaned up. Every setInterval needs a corresponding clearInterval on shutdown.
Prisma connection exhaustion: If the AI creates a new PrismaClient per request instead of using a singleton, you will exhaust the database connection pool. Always verify it uses the singleton pattern.
Environment variable access: If the AI accesses process.env directly throughout the code, it is hard to test and easy to miss missing variables. Prefer a validated config module loaded at startup.