Best practices para APIs RESTful con Node.js
dev

Best practices para APIs RESTful con Node.js

Guía completa para diseñar y construir APIs RESTful escalables con Node.js

Best practices para APIs RESTful con Node.js
4 de enero de 2024
5 min de lectura
Compartir:

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.