Best practices para APIs RESTful con Node.js
Construir APIs RESTful robustas es fundamental para el desarrollo moderno. Aquí comparto las mejores prácticas que he aprendido desarrollando APIs que manejan miles de requests por minuto.
1. Estructura del proyecto
Una buena estructura es la base de un API mantenible:
src/
├── controllers/
│ ├── userController.js
│ └── productController.js
├── middleware/
│ ├── auth.js
│ └── validation.js
├── routes/
│ ├── users.js
│ └── products.js
├── services/
│ ├── userService.js
│ └── productService.js
├── models/
│ ├── User.js
│ └── Product.js
├── utils/
│ ├── logger.js
│ └── errorHandler.js
└── app.js
2. Validación robusta con Joi
Nunca confíes en los datos del cliente:
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(3).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required()
});
// Middleware de validación
const validateUser = (req, res, next) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({
error: error.details[0].message
});
}
next();
};
3. Manejo centralizado de errores
const errorHandler = (err, req, res, next) => {
logger.error(err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({
error: 'Validation Error',
details: err.message
});
}
if (err.code === 11000) {
return res.status(409).json({
error: 'Duplicate Entry',
details: 'Resource already exists'
});
}
res.status(500).json({
error: 'Internal Server Error'
});
};
4. Rate limiting para prevenir abusos
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
5. Autenticación con JWT
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const generateToken = (user) => {
return jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
};
const authMiddleware = async (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
6. Database connection pooling
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
module.exports = pool;
7. Logging estructurado
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
8. API Documentation con Swagger
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'My API',
version: '1.0.0',
},
},
apis: ['./src/routes/*.js'],
};
const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
9. Caching con Redis
const redis = require('redis');
const client = redis.createClient();
const cache = async (req, res, next) => {
const key = req.originalUrl;
const data = await client.get(key);
if (data) {
return res.json(JSON.parse(data));
}
res.sendResponse = res.json;
res.json = (body) => {
client.set(key, JSON.stringify(body), 'EX', 3600); // Cache for 1 hour
res.sendResponse(body);
};
next();
};
10. Environment variables
require('dotenv').config();
const config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
jwtSecret: process.env.JWT_SECRET,
dbHost: process.env.DB_HOST,
dbUser: process.env.DB_USER,
dbPassword: process.env.DB_PASSWORD,
dbName: process.env.DB_NAME
};
Conclusión
Estas prácticas te ayudarán a construir APIs más robustas, seguras y mantenibles. Recuerda que la calidad del código se refleja en la experiencia del usuario final.
Si quieres profundizar en alguno de estos temas, déjame un comentario y puedo dedicar un post completo a ello.