REST API Best Practices
This page consolidates everything from the REST section into a practical checklist — the conventions that separate an API people enjoy consuming from one they fight. None of it is new; it's the disciplined application of design, status codes, validation, errors, pagination, versioning, and security covered across this section.
Design & naming
Resource-based, plural nouns:
/users,/orders/9/items— never verbs in the path.Use HTTP methods for actions; respect their safe/idempotent semantics.
Lowercase, hyphenated paths; no file extensions; consistent trailing-slash policy.
Cap nesting at one level; filter/sort/paginate via the query string.
Version from day one (
/v1/...) and only bump on breaking changes.
Status codes & responses
Situation | Status | Rule |
|---|---|---|
Created a resource |
|
|
Successful read/update |
| Return the resource |
Successful delete |
| Empty body |
Validation failed |
| Field-level details |
Not authenticated / not allowed |
| Don't conflate them |
Server fault |
| Generic message; log internally |
Security essentials
Validate every input — body, params, query, headers; whitelist fields to stop mass assignment.
Authenticate then authorize — both, in that order, before handlers.
HTTPS only in production;
helmet()for security headers.Rate limit sensitive and expensive endpoints.
CORS whitelisted to known origins; never
*with credentials.Cap body size (
limit) and upload size; reject oversized payloads with413.Never trust the client — re-validate server-side; client checks are UX only.
Performance & scale
Paginate every list with a capped
limit; cursor pagination for large/live data.Set
Cache-Controlon cacheableGETs;no-storeon private data.Index the columns you filter, sort, and paginate on.
Stay stateless so you can run many instances behind a load balancer.
Use a shared store (Redis) for rate limits and sessions across instances.
Compress responses once — in Node or the proxy, not both.
Reliability & operations
Centralize errors in one error-handling middleware; log
5xxfully.Make idempotent operations truly idempotent; add idempotency keys for critical
POSTs.Add a health-check endpoint (
GET /health) for load balancers.Attach a request id to every request and log it for tracing.
Document with OpenAPI, generated from code where possible.
A reference structure to copy
The shape of a production Express API
const app = express()
// ── Security & infrastructure (first) ──
app.use(helmet())
app.use(cors({ origin: allowedOrigins, credentials: true }))
app.use(compression())
app.use(morgan('combined'))
app.use(requestId())
// ── Parsing & limits ──
app.use(express.json({ limit: '100kb' }))
app.use(cookieParser())
// ── Rate limiting ──
app.use('/api', rateLimit({ windowMs: 60_000, max: 100 }))
// ── Versioned routes ──
app.use('/v1', v1Router)
// ── 404, then the central error handler (last) ──
app.use(notFoundHandler)
app.use(errorHandler)