Configuration can make or break a REST API. Small missteps in timeouts, CORS, body size limits, auth, or connection pools can lead to intermittent failures that are hard to trace. This guide focuses on practical mistakes, how to validate changes safely, how to roll back, and how to troubleshoot quickly. Examples use Express on Node.js, MongoDB, and integrations such as Stripe and the OpenAI API.
Who this is for:
- Developers growing an Express or Node.js service
- DevOps consultants standardizing API configs across services
- Startup teams adding third-party integrations under time pressure
What you get:
- A checklist of common REST API configuration mistakes
- Concrete code and config examples
- A safe change workflow, a small pilot plan, and a rollback habit
---
Common configuration mistakes with practical examples
Below are mistakes seen in production APIs, with fixes you can apply.
1) CORS: permissive defaults or inconsistent allowlists
Symptom:
- Everything works in local tests but browsers fail with opaque CORS errors.
Bad:
// Express
const cors = require('cors');
app.use(cors()); // Allows any origin, which is risky and can hide bugs
Better (explicit allowlist and methods):
const cors = require('cors');
const allowed = [
'https://app.example.com',
'https://admin.example.com'
];
app.use(cors({
origin: (origin, cb) => {
if (!origin || allowed.includes(origin)) return cb(null, true);
return cb(new Error('Not allowed by CORS'));
},
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
credentials: true
}));
Validation:
- Verify preflight:
curl -i -X OPTIONS https://api.example.com/v1/items -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: GET" - Confirm disallowed origins are rejected with 403 or a clear error.
Rollback:
- Keep previous CORS config in version control; toggle back if login flows break.
2) Body parser limits: accepting too much or too little
Symptom:
- Users uploading JSON payloads see 413 errors (too large) or requests hang.
Fix:
const express = require('express');
const app = express();
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: false, limit: '1mb' }));
Tip:
- Align reverse proxy limits (for example, client body size) with Express limits.
Validation:
- Upload a payload just under the limit and just over it; confirm 200 vs 413.
Rollback:
- Revert the limit commit and restart only the affected instance group.
3) Timeouts: mismatched client, server, and proxy settings
Symptoms:
- Intermittent 499/504/408 errors, webhook retries from Stripe, or client-side aborts.
Server-side example (Node 18+):
const server = app.listen(process.env.PORT || 3000);
server.requestTimeout = 60000; // 60s for full request lifecycle
server.headersTimeout = 65000; // 65s to avoid header-timeout race
Reverse proxy example (generic):
# Ensure proxy and app timeouts align (example values)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
Third-party calls:
- Set outbound client timeouts too, so your API does not hang on slow upstreams.
Validation:
- Simulate slow handlers and confirm the system returns a controlled timeout.
Rollback:
- Restore previous timeout values and redeploy the config only, not the code.
4) Rate limiting: missing, inconsistent, or too strict
Symptoms:
- Users burst a few requests and start seeing 429s; or your API is open to abuse.
Fix with Express:
const rateLimit = require('express-rate-limit');
app.use('/v1/', rateLimit({
windowMs: 60 * 1000,
max: 120,
standardHeaders: true,
legacyHeaders: false
}));
Validation:
- Send 130 requests in 60s from one IP; confirm 120 succeed, 10 are 429.
Rollback:
- Increase the limit or window; remove the middleware if critical flows break.
5) Auth and secrets: wrong environment keys or missing checks
Symptoms:
- Payment attempts fail; webhook verification fails; AI calls return 401.
Guards for Stripe and OpenAI keys:
if (process.env.NODE_ENV === 'production') {
if (process.env.STRIPE_SECRET_KEY?.startsWith('sk_test_')) {
throw new Error('Refusing to start with Stripe test key in production');
}
}
if (!process.env.OPENAI_API_KEY) {
throw new Error('OPENAI_API_KEY is required');
}
Webhook signature verification (Stripe):
// Keep raw body for signature verification
a pp.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// handle event
res.sendStatus(200);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
});
Validation:
- Use known-test events and verify signature checks pass and fail as expected.
Rollback:
- Keep old secret and endpoint live until traffic confirms the new one works.
6) Express behind a proxy: missing trust and broken secure cookies
Symptoms:
- Users never stay logged in;
Set-CookiewithSecureis not honored.
Fix:
app.set('trust proxy', 1); // if there is exactly one reverse proxy
// or app.set('trust proxy', true) when multiple proxies are present
Ensure secure cookie settings once trust is correct:
app.use(require('cookie-session')({
name: 'sid',
secret: process.env.SESSION_SECRET,
cookie: { secure: true, sameSite: 'lax', httpOnly: true }
}));
Validation:
- Confirm
X-Forwarded-Proto: httpsis set by the proxy and Express respects it.
Rollback:
- Revert
trust proxychange if cookie behavior regresses; inspect proxy headers.
7) MongoDB connection and pool sizing issues
Symptoms:
- Spiky latency, timeouts during traffic bursts, or connection storms on deploys.
Connection example:
const { MongoClient } = require('mongodb');
const client = new MongoClient(process.env.MONGODB_URI, {
maxPoolSize: 20,
minPoolSize: 5,
serverSelectionTimeoutMS: 5000,
retryWrites: true
});
Operational tips:
- Use one client per process and reuse connections.
- Set reasonable pool sizes; too large can overwhelm the server.
- Confirm indexes exist for hot queries; misconfigured indexes look like app bugs.
Validation:
- Load test with bursty traffic and watch connection utilization and latency.
Rollback:
- Restore prior pool sizes; restart a single instance first to observe effects.
8) Pagination defaults and response size
Symptoms:
- Clients fetch too much data by default; latency and memory spike.
Fix:
const DEFAULT_PAGE_SIZE = 50;
const MAX_PAGE_SIZE = 200;
app.get('/v1/items', async (req, res) => {
const page = Math.max(1, parseInt(req.query.page || '1', 10));
const pageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, parseInt(req.query.pageSize || DEFAULT_PAGE_SIZE, 10)));
// query with limit/skip based on page and pageSize
});
Validation:
- Ensure responses include
total,page, andpageSizeso clients can adapt.
Rollback:
- If clients break, reintroduce the old default and schedule a migration path.
9) Logging and redaction levels
Symptoms:
- Either no data to debug incidents or sensitive data leaked in logs.
Fix:
- Set
LOG_LEVEL=infoin production and ensure token redaction. - Sample debug logs only for targeted endpoints during incident windows.
Validation:
- Confirm sensitive headers (Authorization) are redacted.
Rollback:
- Lower verbosity if logging floods storage or raises costs.
---
Workflow Overview
Adopt a simple, repeatable flow for configuration changes.
- Baseline and diff
- Capture current config and runtime values (timeouts, limits, keys present, proxy headers).
- Decide the minimal change you will make and why.
- Change in one place
- Modify a single control at a time (for example, CORS allowlist on one route).
- Keep the change behind a flag or route-specific middleware when possible.
- Validate with repeatable checks
- Functional: status codes, payload sizes, CORS preflight, authentication flows.
- Non-functional: latency, error rate, memory, open connections.
- Integration: Stripe test charge, OpenAI small completion, MongoDB read/write.
- Deploy in stages
- Start with one instance or one environment.
- Watch metrics and logs before expanding.
- Observe and decide
- Define up-front success and rollback thresholds (for example, 99.9% success, p95 latency < 300 ms, 429 rate < 1%).
- Roll back fast
- Revert config commits or toggle flags.
- Announce outcome and next steps, then schedule a deeper fix if needed.
This separation of steps reduces back-and-forth and helps teams avoid broad, risky changes.
---
Local Pilot Plan
Goal: deliver a narrow, measurable improvement you can inspect on a workstation before broader rollout.
Pilot example: tighten body size limits and add a CORS allowlist on one resource.
Scope
- Route:
POST /v1/uploads - Changes:
express.json({ limit: '1mb' }), allowlist origins for uploads UI - Metrics: 2xx rate, 4xx distribution (especially 413 and 403), p95 latency, payload sizes
Steps
- Prepare
- Add config with sensible defaults and env overrides.
- Add metrics or logs to observe request size and origin.
- Test on a workstation
- Payload size checks:
curl -s -o /dev/null -w "%{http_code}\n" -H "Content-Type: application/json" --data @payload-900kb.json https://api.example.com/v1/uploads- Expect 200
- Repeat with
payload-1100kb.json, expect 413 - CORS preflight:
curl -i -X OPTIONS https://api.example.com/v1/uploads -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST"
- Staged rollout
- Enable only on
POST /v1/uploads. - Observe metrics for 30-60 minutes; compare before/after.
- Decision
- If success metrics hold and error rates do not rise, extend to sibling routes.
- If not, roll back and analyze logs for mismatched clients or proxy limits.
Rollback
- Revert the config change; confirm previous behavior within minutes.
- Document the issue and the client types that were affected.
---
Troubleshooting workflows
Use these quick paths when something goes wrong after a config change.
- Confirm the symptom
- Exact endpoint, method, status code, and time range.
- Reproduce with headers
curl -v -H "Origin: https://app.example.com" -H "Accept: application/json" -H "Content-Type: application/json" https://api.example.com/v1/items- Check response headers (CORS, cache, content-type) and timing.
- Check logs and metrics
- Look for spikes in 4xx/5xx, 429s, timeouts, and memory or connection pool saturation.
- Inspect boundaries
- Reverse proxy limits and timeouts vs app timeouts.
- TLS termination and
X-Forwarded-*headers.
- Validate third-party assumptions
- Stripe: key type matches environment; webhook signature verifies.
- OpenAI: API key present; request size and timeouts within your limits.
- Database connectivity
- Verify pool size, server selection timeout, and authentication in the connection string.
- Last-resort rollback
- Prefer flipping a config flag or reverting a single commit over hotfix coding.
---
Conclusion
Small, explicit configuration changes beat broad, risky edits. Use allowlists for CORS, set sensible body and timeouts, right-size connection pools, and guard secrets. Validate with repeatable checks, deploy in stages, and keep rollback simple and fast. Start with a narrow pilot, measure real effects, then expand with confidence.