What makes an API production-ready
Production-ready is not about using any specific framework or library. It is about the properties the system has: it handles unexpected inputs gracefully, degrades intelligently under load, surfaces problems before they become user-visible outages, and makes it easy to understand what happened after the fact.
Here are the decisions that matter most.
Input validation at the boundary
Validate every piece of external input at the entry point — before it touches any business logic. The body of an incoming request should never be trusted. Use a schema validation library (Zod is excellent for TypeScript projects) to define the expected shape of every request and reject malformed input with a clear 400 response.
This is not just about security — it is about maintaining API contracts. When validation is strict and consistent, you catch integration bugs early and your error logs contain actionable information rather than undefined property stack traces.
Consistent error response format
Nothing frustrates API consumers more than inconsistent error responses. Define a standard error envelope at the start of the project and use it everywhere:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body is invalid",
"details": [{"field": "email", "message": "Must be a valid email address"}]
}
}Map HTTP status codes consistently. 400 for client errors, 401/403 for auth failures, 422 for semantic validation errors, 429 for rate limiting, 500 for server errors. Never return 200 with an error body — that pattern breaks every HTTP-aware client and monitoring tool.
Rate limiting
Every public endpoint needs rate limiting. The question is not whether to add it but at what granularity. IP-based rate limiting at the gateway level protects against obvious abuse. Per-user rate limiting at the application level protects your database from legitimate users who have written inefficient client code or automation scripts.
For Node.js specifically, use a token bucket algorithm backed by Redis rather than in-memory limits — in-memory limits reset on restart and do not work across multiple instances.
Database connection pooling
One of the most common production failure modes for Node.js APIs: the database connection pool exhausts under load. Set explicit pool sizes, configure connection timeouts, and log pool events so you know when you are approaching saturation before you run out.
For PostgreSQL with Prisma or TypeORM, the default pool size of 5 connections is appropriate for development. In production, match your pool size to the number of CPU cores × 2, and set a connection timeout that fails fast rather than queuing forever.
Structured logging
Console.log is not logging. Production-ready APIs emit structured JSON logs that include correlation IDs (so you can trace a request through multiple services), user context (so you can see what a specific user experienced), and timing information.
Pino is the standard choice for high-performance structured logging in Node.js. Pair it with a log aggregation service (Datadog, Grafana Loki, CloudWatch) and set up alerts on error rate, p99 latency, and slow query counts.
Health and readiness endpoints
Every API needs: - /health — returns 200 if the application process is running - /ready — returns 200 only if all dependencies (database, external APIs) are available
Orchestration systems (Kubernetes, ECS) use these to route traffic and restart unhealthy instances. Without them, you will route requests to instances that are up but broken.
Graceful shutdown
Handle SIGTERM. When your deployment system restarts your API, in-flight requests should complete before the process exits. A graceful shutdown handler that stops accepting new connections, waits for active requests to finish, and then closes database connections is essential for zero-downtime deployments.