Table of Contents
Building Production-Ready APIs: Best Practices from 100+ Projects
Hey Fellow Builder! š
After 5 years of building APIs for startups, enterprises, and side projects, I've learned some hard lessons. This week, I'm sharing the most impactful patterns and practices that have saved me countless hours (and prevented many 3 AM emergency calls).
š Today's Deep Dive
- Error Handling that actually helps debugging
- Rate Limiting strategies that work
- Security patterns beyond the basics
- Performance optimizations for scale
- Real code examples from production systems
---
Error Handling: The Foundation
The difference between a good API and a great one? Error handling. Here's my battle-tested approach:
1// ā What most developers do2app.post('/users', async (req, res) => {3 try {4 const user = await User.create(req.body);5 res.json(user);6 } catch (error) {7 res.status(500).json({ error: 'Something went wrong' });8 }9});10Ā 11// ā
Production-ready error handling12class APIError extends Error {13 constructor(message, statusCode = 500, code = null) {14 super(message);15 this.statusCode = statusCode;16 this.code = code;17 this.isOperational = true;18 }19}20Ā 21const errorHandler = (err, req, res, next) => {22 // Log for debugging23 console.error({24 error: err.message,25 stack: err.stack,26 url: req.url,27 method: req.method,28 ip: req.ip,29 userAgent: req.get('User-Agent'),30 timestamp: new Date().toISOString()31 });32Ā 33 // Respond based on error type34 if (err.isOperational) {35 return res.status(err.statusCode).json({36 success: false,37 error: {38 message: err.message,39 code: err.code,40 timestamp: new Date().toISOString()41 }42 });43 }44Ā 45 // Don't leak internal errors46 res.status(500).json({47 success: false,48 error: {49 message: 'Internal server error',50 code: 'INTERNAL_ERROR',51 timestamp: new Date().toISOString()52 }53 });54};55Ā 56// Usage in routes57app.post('/users', async (req, res, next) => {58 try {59 const { email, password } = req.body;60Ā 61 if (!email) {62 throw new APIError('Email is required', 400, 'MISSING_EMAIL');63 }64Ā 65 if (await User.findOne({ email })) {66 throw new APIError('Email already exists', 409, 'EMAIL_EXISTS');67 }68Ā 69 const user = await User.create({ email, password });70 res.status(201).json({ success: true, data: user });71 } catch (error) {72 next(error);73 }74});Key insight: Consistent error structure makes frontend integration seamless.
---
š”ļø Rate Limiting That Actually Works
Basic rate limiting isn't enough. Here's my tiered approach:
1const redis = require('redis');2const client = redis.createClient();3Ā 4class SmartRateLimit {5 constructor() {6 this.limits = {7 // Per endpoint limits8 'POST:/auth/login': { window: 900, max: 5 }, // 5 attempts per 15 min9 'POST:/users': { window: 3600, max: 10 }, // 10 signups per hour10 'GET:/api/v1/*': { window: 60, max: 100 }, // 100 reads per minute11 'POST:/api/v1/*': { window: 60, max: 20 }, // 20 writes per minute12Ā 13 // User-based limits14 'user:free': { window: 3600, max: 1000 }, // 1k requests/hour15 'user:premium': { window: 3600, max: 10000 }, // 10k requests/hour16 };17 }18Ā 19 async checkLimit(identifier, limitType) {20 const limit = this.limits[limitType];21 if (!limit) return { allowed: true };22Ā 23 const key = `rate_limit:${limitType}:${identifier}`;24 const current = await client.incr(key);25Ā 26 if (current === 1) {27 await client.expire(key, limit.window);28 }29Ā 30 const ttl = await client.ttl(key);31Ā 32 return {33 allowed: current <= limit.max,34 remaining: Math.max(0, limit.max - current),35 resetTime: Date.now() + (ttl * 1000),36 limit: limit.max37 };38 }39}40Ā 41// Middleware implementation42const rateLimit = (limitType) => async (req, res, next) => {43 const identifier = req.ip; // or req.user?.id for authenticated requests44 const result = await rateLimiter.checkLimit(identifier, limitType);45Ā 46 // Add headers for API consumers47 res.set({48 'X-RateLimit-Limit': result.limit,49 'X-RateLimit-Remaining': result.remaining,50 'X-RateLimit-Reset': result.resetTime51 });52Ā 53 if (!result.allowed) {54 return res.status(429).json({55 success: false,56 error: {57 message: 'Rate limit exceeded',58 code: 'RATE_LIMIT_EXCEEDED',59 retryAfter: result.resetTime60 }61 });62 }63Ā 64 next();65};66Ā 67// Usage68app.post('/auth/login', rateLimit('POST:/auth/login'), loginHandler);69app.use('/api/v1', rateLimit('GET:/api/v1/*'), apiRoutes);Pro tip: Different limits for different user tiers = instant monetization opportunity.
---
š Security Beyond Authentication
Authentication is just the start. Here's my security checklist:
1// 1. Input Validation & Sanitization2const { body, validationResult } = require('express-validator');3Ā 4const validateUser = [5 body('email').isEmail().normalizeEmail(),6 body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/),7 body('name').trim().escape().isLength({ min: 2, max: 50 }),8Ā 9 (req, res, next) => {10 const errors = validationResult(req);11 if (!errors.isEmpty()) {12 return res.status(400).json({13 success: false,14 error: {15 message: 'Validation failed',16 details: errors.array()17 }18 });19 }20 next();21 }22];23Ā 24// 2. SQL Injection Prevention25// ā Never do this26const getUserById = (id) => {27 return db.query(`SELECT * FROM users WHERE id = ${id}`);28};29Ā 30// ā
Always use parameterized queries31const getUserById = (id) => {32 return db.query('SELECT * FROM users WHERE id = ?', [id]);33};34Ā 35// 3. Authorization Middleware36const authorize = (permissions = []) => {37 return async (req, res, next) => {38 try {39 const token = req.headers.authorization?.split(' ')[1];40 if (!token) {41 throw new APIError('Access token required', 401, 'MISSING_TOKEN');42 }43Ā 44 const decoded = jwt.verify(token, process.env.JWT_SECRET);45 const user = await User.findById(decoded.userId).populate('role');46Ā 47 if (!user) {48 throw new APIError('Invalid token', 401, 'INVALID_TOKEN');49 }50Ā 51 // Check permissions52 if (permissions.length > 0) {53 const hasPermission = permissions.some(p => 54 user.role.permissions.includes(p)55 );56Ā 57 if (!hasPermission) {58 throw new APIError('Insufficient permissions', 403, 'FORBIDDEN');59 }60 }61Ā 62 req.user = user;63 next();64 } catch (error) {65 next(error);66 }67 };68};69Ā 70// Usage71app.get('/admin/users', authorize(['read:users']), getUsersHandler);72app.delete('/admin/users/:id', authorize(['delete:users']), deleteUserHandler);---
ā” Performance Optimization Secrets
These patterns have saved my APIs during traffic spikes:
1// 1. Smart Caching Strategy2const NodeCache = require('node-cache');3const cache = new NodeCache({ stdTTL: 600 }); // 10 minutes default4Ā 5const cacheMiddleware = (duration = 600, keyGenerator = null) => {6 return async (req, res, next) => {7 const key = keyGenerator 8 ? keyGenerator(req) 9 : `${req.method}:${req.originalUrl}:${JSON.stringify(req.query)}`;10Ā 11 const cached = cache.get(key);12 if (cached) {13 res.set('X-Cache', 'HIT');14 return res.json(cached);15 }16Ā 17 // Override res.json to cache response18 const originalJson = res.json;19 res.json = function(data) {20 if (res.statusCode === 200) {21 cache.set(key, data, duration);22 }23 res.set('X-Cache', 'MISS');24 return originalJson.call(this, data);25 };26Ā 27 next();28 };29};30Ā 31// 2. Database Query Optimization32// ā N+1 Problem33const getBlogPosts = async () => {34 const posts = await Post.find({});35 for (const post of posts) {36 post.author = await User.findById(post.authorId); // N+1!37 }38 return posts;39};40Ā 41// ā
Optimized with eager loading42const getBlogPosts = async () => {43 return Post.find({})44 .populate('author', 'name email avatar')45 .select('title content createdAt author')46 .sort({ createdAt: -1 })47 .limit(20);48};49Ā 50// 3. Response Compression & Pagination51const getPaginatedResults = async (req, res) => {52 const page = parseInt(req.query.page) || 1;53 const limit = Math.min(parseInt(req.query.limit) || 10, 100); // Max 10054 const skip = (page - 1) * limit;55Ā 56 const [data, total] = await Promise.all([57 Model.find(filter).skip(skip).limit(limit),58 Model.countDocuments(filter)59 ]);60Ā 61 res.json({62 success: true,63 data,64 pagination: {65 page,66 limit,67 total,68 pages: Math.ceil(total / limit),69 hasNext: page < Math.ceil(total / limit),70 hasPrev: page > 171 }72 });73};---
š Monitoring & Observability
You can't fix what you can't see:
1// Request logging middleware2const requestLogger = (req, res, next) => {3 const start = Date.now();4Ā 5 res.on('finish', () => {6 const duration = Date.now() - start;7 const log = {8 method: req.method,9 url: req.originalUrl,10 status: res.statusCode,11 duration,12 ip: req.ip,13 userAgent: req.get('User-Agent'),14 timestamp: new Date().toISOString()15 };16Ā 17 // Log slow requests18 if (duration > 1000) {19 console.warn('Slow request:', log);20 }21Ā 22 // Send to monitoring service23 if (process.env.NODE_ENV === 'production') {24 // analytics.track('api_request', log);25 }26 });27Ā 28 next();29};30Ā 31// Health check endpoint32app.get('/health', async (req, res) => {33 const checks = {34 timestamp: new Date().toISOString(),35 uptime: process.uptime(),36 memory: process.memoryUsage(),37 database: await checkDatabaseConnection(),38 redis: await checkRedisConnection(),39 version: process.env.npm_package_version40 };41Ā 42 const isHealthy = checks.database && checks.redis;43Ā 44 res.status(isHealthy ? 200 : 503).json({45 status: isHealthy ? 'healthy' : 'unhealthy',46 checks47 });48});---
Quick Wins for This Week
- Add structured error handling to your current API
- Implement request logging to see what's actually happening
- Add a health check endpoint for monitoring
- Review your rate limiting strategy
---
š Essential Tools & Resources
- Documentation: Swagger/OpenAPI
- Security: OWASP API Security Top 10
---
š Community Success
Emily Zhang shipped her first production API this week using these patterns! It's handling 10k+ requests/day flawlessly. Amazing work, Emily!
---
That's Issue #3 in the books! š
APIs are the backbone of modern applications - investing time in getting them right pays dividends for years.
What's your biggest API challenge right now? Reply and tell me - I might feature the solution in a future issue!
Keep building,
Mantej Singh *API Architect & Full-Stack Developer*
P.S. - Next week: "Database Performance: From Slow Queries to Lightning Fast" - Including real optimization case studies! ā”
---
Found this helpful? Share it with your team! [Unsubscribe](mailto:unsubscribe@mantej.in) | [Archive](https://mantej.in/newsletter)

