ConfigurationEnvironment Configuration & Security
Local development and production are completely different environments. Your local app connects to a database running on your laptop. Production connects to a database cluster on AWS. These differences must not be baked into your code — they must be configured through environment variables. This module covers professional configuration management and the security practices that separate amateur backends from production-grade ones.
Rate Limiting — Protecting Your API from Abuse
Without rate limiting, a single attacker can send millions of requests per second to your API — brute-forcing passwords, scraping all your data, or simply crashing your server with load. Rate limiting restricts how many requests a single IP address can make in a given time window.
const rateLimit = require('express-rate-limit');
// General API rate limit: 100 requests per 15 minutes
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: { error: 'Too many requests, please try again later.' },
standardHeaders: true, // Send RateLimit headers in the response
legacyHeaders: false
});
// Strict limit for auth endpoints (brute force protection)
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Only 10 login attempts per 15 minutes per IP
message: { error: 'Too many login attempts. Please wait 15 minutes.' },
skipSuccessfulRequests: true // Don't count successful logins against the limit
});
// Apply globally
app.use('/api/', generalLimiter);
// Apply strictly to auth routes
app.use('/auth/login', authLimiter);
app.use('/auth/register', authLimiter);
Input Validation — Never Trust User Input
Every piece of data that arrives from a client must be treated as potentially malicious until proven otherwise. Validation confirms that the data matches what you expect before it touches your database or business logic. The Zod library makes this elegant and type-safe in Node.js:
const { z } = require('zod');
// Define a schema for the expected request body
const registerSchema = z.object({
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(50, 'Username cannot exceed 50 characters')
.regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, underscores'),
email: z.string()
.email('Invalid email address'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
.regex(/[0-9]/, 'Password must contain at least one number'),
age: z.number()
.int()
.min(13, 'Must be at least 13 years old')
.optional()
});
// Validation middleware
app.post('/auth/register', (req, res, next) => {
const result = registerSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.errors.map(e => ({
field: e.path.join('.'),
message: e.message
}))
});
}
req.validatedBody = result.data; // Only safe, validated data passes through
next();
}, async (req, res) => {
const { username, email, password } = req.validatedBody;
// ... create user with validated, safe data
});
For Python, the Pydantic library provides similar validation capabilities with type hints:
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')
email: EmailStr
password: str = Field(..., min_length=8)
age: Optional[int] = Field(None, ge=13, le=150)
Security Headers with Helmet
HTTP headers control a lot of browser security behavior. Helmet configures these headers to strict, safe defaults with one line of code.
const helmet = require('helmet');
// helmet() sets all these security headers automatically
app.use(helmet());
// What helmet actually sets:
// X-Content-Type-Options: nosniff — Prevent MIME type sniffing
// X-Frame-Options: SAMEORIGIN — Prevent clickjacking
// X-XSS-Protection: 1; mode=block — Legacy XSS protection
// Strict-Transport-Security: max-age=... — Force HTTPS for 1 year
// Content-Security-Policy: default-src 'self' — Prevent script injection
// Referrer-Policy: no-referrer — Don't leak referrer URLs
💡CORS Configuration: Configure CORS explicitly — never use origin: '*' in production. Whitelist only your specific frontend domain. This prevents unauthorized websites from calling your API on behalf of logged-in users (CSRF attacks).
app.use(cors({
origin: process.env.CORS_ORIGIN, // e.g., 'https://app.voidx.io'
credentials: true // Allow cookies to be sent cross-origin
}));
Security Checklist Summary
- ✅ Always use HTTPS — Never serve production APIs over HTTP.
- ✅ Rate limit authentication routes — 10 attempts per 15 minutes maximum.
- ✅ Validate all user input — Use Zod (Node) or Pydantic (Python).
- ✅ Set security headers — Helmet for Node.js, `SecurityMiddleware` for Flask.
- ✅ Configure CORS properly — Whitelist your exact frontend domain, never use `*`.
- ✅ Never commit .env files — Use environment variables for all secrets.
- ✅ Hash passwords with bcrypt — 12 salt rounds minimum.
- ✅ Add X-Request-ID headers — For tracing requests through logs.