Introduction
Building a robust and scalable API is the foundation of any modern application. In this article, we will explore the best practices for building APIs using Node.js and Express.
Project Structure
src/
├── controllers/ # Business logic
├── routes/ # Route definitions
├── middleware/ # Middleware
├── models/ # Database models
├── services/ # External services
├── utils/ # Utility functions
└── app.js # Entry point
Error Handling
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
const errorHandler = (err, req, res, next) => {
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
success: false,
error: {
message: err.message,
code: err.code || 'INTERNAL_ERROR'
}
});
};
Data Validation
Use libraries like Zod or Joi for validation:
const { z } = require('zod');
const userSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
name: z.string().min(2).max(100)
});
const validateUser = (req, res, next) => {
try {
userSchema.parse(req.body);
next();
} catch (error) {
res.status(400).json({ errors: error.errors });
}
};
Caching
Use Redis for caching:
const redis = require('redis');
const client = redis.createClient();
const cacheMiddleware = async (req, res, next) => {
const key = req.originalUrl;
const cached = await client.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
res.originalJson = res.json;
res.json = (data) => {
client.setEx(key, 3600, JSON.stringify(data));
res.originalJson(data);
};
next();
};
Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per IP
message: { error: 'Too many requests, please try again later' }
});
app.use('/api/', limiter);
Monitoring and Logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
Conclusion
Building a scalable API requires good planning and adherence to best practices. Focus on:
- Clear project structure
- Comprehensive error handling
- Input validation
- Caching
- Continuous monitoring