El desarrollo de APIs RESTful bien diseñadas es fundamental en la arquitectura moderna de aplicaciones. En este artículo exploraremos las mejores prácticas y patrones para crear APIs robustas y escalables.

Principios Fundamentales de REST

1. Recursos como Sustantivos

Las URLs deben representar recursos, no acciones:

✅ Correcto:
GET /api/users/123
POST /api/orders
PUT /api/products/456

❌ Incorrecto:
GET /api/getUser/123
POST /api/createOrder
PUT /api/updateProduct/456

2. Uso Correcto de Métodos HTTP

  • GET: Obtener recursos (idempotente)
  • POST: Crear nuevos recursos
  • PUT: Actualizar recursos completos (idempotente)
  • PATCH: Actualizaciones parciales
  • DELETE: Eliminar recursos (idempotente)

3. Códigos de Estado Apropiados

// Respuestas exitosas
200 OK          // Solicitud exitosa
201 Created     // Recurso creado
204 No Content  // Eliminación exitosa

// Errores del cliente
400 Bad Request      // Datos inválidos
401 Unauthorized     // No autenticado
403 Forbidden        // No autorizado
404 Not Found        // Recurso no existe

// Errores del servidor
500 Internal Error   // Error interno
503 Service Unavailable // Servicio no disponible

Estructura de Respuestas Consistente

Respuestas de Éxito

{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "meta": {
    "timestamp": "2024-03-10T10:00:00Z",
    "version": "v1"
  }
}

Respuestas de Error

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Los datos proporcionados son inválidos",
    "details": [
      {
        "field": "email",
        "message": "Formato de email inválido"
      }
    ]
  },
  "meta": {
    "timestamp": "2024-03-10T10:00:00Z",
    "requestId": "req-123456"
  }
}

Versionado de APIs

1. Versionado por URL

GET /api/v1/users
GET /api/v2/users

2. Versionado por Header

GET /api/users
Accept: application/vnd.api+json;version=1

3. Versionado por Parámetro

GET /api/users?version=1

Recomendación: Usar versionado por URL para simplicidad y claridad.

Autenticación y Seguridad

JWT (JSON Web Tokens)

// Ejemplo de implementación con Node.js
const jwt = require('jsonwebtoken');

// Generar token
const token = jwt.sign(
  { userId: 123, role: 'admin' },
  process.env.JWT_SECRET,
  { expiresIn: '1h' }
);

// Middleware de verificación
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Token requerido' });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).json({ error: 'Token inválido' });
    req.user = user;
    next();
  });
}

Mejores Prácticas de Seguridad

  • HTTPS: Siempre usar conexiones seguras
  • Rate Limiting: Limitar solicitudes por IP/usuario
  • Validación de Entrada: Validar y sanitizar todos los datos
  • CORS: Configurar correctamente Cross-Origin Resource Sharing
  • Headers de Seguridad: Implementar headers como HSTS, CSP, etc.

Paginación y Filtrado

Paginación

// Parámetros de consulta
GET /api/users?page=1&limit=20&sort=name&order=asc

// Respuesta con metadatos de paginación
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 250,
    "pages": 13,
    "hasNext": true,
    "hasPrev": false
  }
}

Filtrado Avanzado

GET /api/products?category=electronics&minPrice=100&maxPrice=500&inStock=true

Documentación con OpenAPI

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: Lista todos los usuarios
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        200:
          description: Lista de usuarios
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'

Optimización de Rendimiento

1. Caching

// Redis para caching
const redis = require('redis');
const client = redis.createClient();

app.get('/api/users/:id', async (req, res) => {
  const cacheKey = `user:${req.params.id}`;
  
  // Verificar cache
  const cached = await client.get(cacheKey);
  if (cached) {
    return res.json(JSON.parse(cached));
  }
  
  // Obtener de base de datos
  const user = await User.findById(req.params.id);
  
  // Guardar en cache (5 minutos)
  await client.setex(cacheKey, 300, JSON.stringify(user));
  
  res.json(user);
});

2. Compresión

const compression = require('compression');
app.use(compression());

3. ETags para Caching Cliente

app.get('/api/users/:id', (req, res) => {
  const user = getUserById(req.params.id);
  const etag = generateETag(user);
  
  res.set('ETag', etag);
  
  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }
  
  res.json(user);
});

Testing de APIs

Pruebas con Jest y Supertest

describe('Users API', () => {
  test('GET /api/users should return users list', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200);
      
    expect(response.body.success).toBe(true);
    expect(Array.isArray(response.body.data)).toBe(true);
  });
  
  test('POST /api/users should create user', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com'
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(201);
      
    expect(response.body.data.name).toBe(userData.name);
  });
});

Monitoreo y Logging

Logging Estructurado

const winston = require('winston');

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'api.log' })
  ]
});

// Middleware de logging
app.use((req, res, next) => {
  logger.info('API Request', {
    method: req.method,
    url: req.url,
    ip: req.ip,
    userAgent: req.get('User-Agent')
  });
  next();
});

Métricas de Rendimiento

const prometheus = require('prom-client');

const httpRequestDuration = new prometheus.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status']
});

// Middleware para métricas
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration
      .labels(req.method, req.route?.path || req.url, res.statusCode)
      .observe(duration);
  });
  
  next();
});

Conclusión

El desarrollo de APIs RESTful de calidad requiere atención a múltiples aspectos: desde el diseño de recursos hasta la implementación de seguridad, rendimiento y monitoreo.

Puntos clave a recordar:

  • Seguir los principios REST consistentemente
  • Implementar autenticación y autorización robustas
  • Documentar thoroughly con OpenAPI
  • Implementar caching y optimizaciones de rendimiento
  • Establecer monitoring y logging comprehensivos
  • Crear tests exhaustivos

En futuros artículos profundizaremos en temas específicos como GraphQL, WebSockets, y arquitecturas de microservicios.