Artículo

Mejores prácticas de diseño de API

Mejores prácticas de diseño de API

Una API (interfaz de programación de aplicaciones) es un contrato entre un proveedor de servicios y sus consumidores. Una API bien diseñada es intuitiva, consistente y fácil de usar. Una API mal diseñada genera desarrolladores frustrados, una mayor carga de soporte, vulnerabilidades de seguridad y cambios importantes costosos.

3 min de lecturaIdioma: ES EspañolGratis0 aplausos0 comentarios
TecnologíaArtículos de ingenieríaGuías de plataformaDesignPracticesWeb DevelopmentTechnologyEngineering ArticlesPlatform Guides
Opciones de lectura

Introducción

Una API (interfaz de programación de aplicaciones) es un contrato entre un proveedor de servicios y sus consumidores. Una API bien diseñada es intuitiva, consistente y fácil de usar. Una API mal diseñada genera desarrolladores frustrados, una mayor carga de soporte, vulnerabilidades de seguridad y cambios importantes costosos.

Las API son productos: diséñelas con el mismo cuidado que las interfaces de usuario. Ya sea que esté creando REST, GraphQL o gRPC, los principios de un buen diseño de API siguen siendo consistentes.


Principios de diseño de API RESTful

1. Utilice sustantivos para recursos

Los recursos son las entidades centrales de su sistema. Nómbrelos con sustantivos, no con verbos:

✅ GET /users              — List users
✅ GET /users/123          — Get user by ID
✅ POST /users             — Create user
✅ PATCH /users/123        — Partially update user
✅ PUT /users/123          — Replace user
✅ DELETE /users/123       — Delete user

❌ GET /getUser            — Verb in URL
❌ POST /createUser        — Verb in URL
❌ POST /users/create      — Verb in URL path
❌ GET /users?action=delete — Action as query parameter

2. Utilice métodos HTTP adecuados

Método Propósito idempotente Seguro cuerpo
OBTENER Recuperar un recurso si si No
PUBLICAR Crear un recurso No No si
PONER Reemplazo completo si No si
PARCHE Actualización parcial No No si
BORRAR Eliminar un recurso si No Opcional

Idempotente: Varias solicitudes idénticas producen el mismo resultado que una sola solicitud.

Seguro: Sin efectos secundarios: se puede almacenar en caché y repetir de forma segura.

3. Convenciones de nomenclatura coherentes

naming_rules:
  - "Use plural nouns: /users, /orders, /products"
  - "Use kebab-case for multi-word: /order-items, /shipping-addresses"
  - "Use lowercase throughout"
  - "Nest resources logically: /users/123/orders"
  - "Avoid deep nesting (max 2-3 levels)"
  - "Use query parameters for filtering, sorting, pagination"

Ejemplo de anidamiento:

/users/123/orders            — Orders belonging to user 123
/users/123/orders/456       — Specific order
/users/123/orders/456/items — Items in that order (okay)
/users/123/orders/456/items/789/details (too deep!)

4. Versione su API

Los cambios importantes deberían incrementar la versión:

Versionado de URI (el más común):

https://api.example.com/v1/users
https://api.example.com/v2/users

Versionado del encabezado:

Accept: application/vnd.example.v1+json

Nunca rompa la compatibilidad con versiones anteriores dentro de la misma versión a menos que:

  • Existe una vulnerabilidad de seguridad crítica.
  • Tiene una política de desaprobación y la ha comunicado.
  • Los usuarios afectados son únicamente internos.

5. Utilice códigos de estado HTTP significativos

2xx Success:
  200 OK: Generic success (GET, PATCH)
  201 Created: Resource created (POST)
  204 No Content: Success, no response body (DELETE)

3xx Redirection:
  301 Moved Permanently
  304 Not Modified (caching)

4xx Client Error:
  400 Bad Request: Malformed request, validation error
  401 Unauthorized: Missing or invalid authentication
  403 Forbidden: Authenticated but not authorized
  404 Not Found: Resource does not exist
  405 Method Not Allowed: Wrong HTTP method
  409 Conflict: Resource state conflict (duplicate, version mismatch)
  422 Unprocessable Entity: Semantic validation failure
  429 Too Many Requests: Rate limit exceeded

5xx Server Error:
  500 Internal Server Error: Unexpected server failure
  502 Bad Gateway: Upstream service failed
  503 Service Unavailable: Temporary overload/maintenance
  504 Gateway Timeout: Upstream service timed out

Diseño de Solicitud y Respuesta

Sobre de respuesta consistente

{
  "data": {
    "id": "user_123",
    "email": "john@example.com",
    "name": "John Doe",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Respuestas de error

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains validation errors",
    "details": [
      {
        "field": "email",
        "message": "Email is required",
        "code": "REQUIRED_FIELD"
      },
      {
        "field": "age",
        "message": "Age must be between 18 and 120",
        "code": "INVALID_VALUE",
        "constraints": {
          "minimum": 18,
          "maximum": 120
        }
      }
    ]
  }
}

Paginación

GET /users?page=2&per_page=20&sort=created_at:desc

{
  "data": [ ... ],
  "meta": {
    "page": 2,
    "per_page": 20,
    "total": 100,
    "total_pages": 5,
    "count": 20
  },
  "links": {
    "first": "/users?page=1&per_page=20",
    "prev": "/users?page=1&per_page=20",
    "next": "/users?page=3&per_page=20",
    "last": "/users?page=5&per_page=20"
  }
}

Paginación basada en cursor (recomendada para conjuntos de datos grandes):

GET /users?cursor=eyJpZCI6IDUwMH0=&limit=20

{
  "data": [ ... ],
  "meta": {
    "next_cursor": "eyJpZCI6IDUyMH0=",
    "has_more": true
  }
}

Filtrar, ordenar y buscar

// Filtering
GET /users?role=admin&status=active
GET /orders?created_at[gte]=2024-01-01&total[gt]=100

// Sorting
GET /users?sort=name:asc,created_at:desc

// Searching
GET /users?search=john

// Field selection (sparse fields)
GET /users/123?fields=id,name,email

Actualizaciones parciales (PATCH vs. PUT)

// PUT — Full replacement (requires all fields)
PUT /users/123
{
  "email": "newemail@example.com",
  "name": "John Updated",
  "age": 31
}

// PATCH — Partial update (only send changed fields)
PATCH /users/123
{
  "email": "newemail@example.com"
}

Mejores prácticas de seguridad

Autenticación

Recomendado: OAuth 2.0 + OpenID Connect

POST /oauth/token
Content-Type: application/json

{
  "grant_type": "client_credentials",
  "client_id": "your_client_id",
  "client_secret": "your_client_secret",
  "scope": "read write"
}

Response:
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read write"
}
GET /api/users
Authorization: Bearer eyJhbGciOi...

Estructura JWT

// Header
{
  "alg": "RS256",
  "typ": "JWT"
}

// Payload
{
  "sub": "user_123",
  "iss": "auth.example.com",
  "iat": 1705312200,
  "exp": 1705315800,
  "scope": "read:users write:users",
  "roles": ["admin"]
}

Limitación de tasa

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705315800
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
Content-Type: application/json

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Try again in 3600 seconds.",
    "retry_after": 3600
  }
}

Validación de entrada

// Zod validation schema for API input
import { z } from 'zod';

const createUserSchema = z.object({
  email: z.string().email('Invalid email format'),
  name: z.string().min(1, 'Name is required').max(100),
  age: z.number().int().min(18).max(120).optional(),
  role: z.enum(['user', 'admin', 'moderator']).default('user'),
  tags: z.array(z.string()).max(10).optional(),
});

// Validation middleware
function validate(schema: ZodSchema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(422).json({
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Validation failed',
          details: result.error.issues.map(i => ({
            field: i.path.join('.'),
            message: i.message
          }))
        }
      });
    }
    req.validatedBody = result.data;
    next();
  };
}

HTTPS y CORS

// CORS configuration
const corsOptions = {
  origin: [
    'https://app.example.com',
    'https://admin.example.com',
  ],
  methods: ['GET', 'POST', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-RateLimit-Remaining'],
  credentials: true,
  maxAge: 86400  // Preflight cache (24 hours)
};
app.use(cors(corsOptions));

Documentación API

Especificación de OpenAPI 3.0

openapi: 3.0.3
info:
  title: User Management API
  version: 2.0.0
  description: API for managing users in the system
  contact:
    email: api-support@example.com

servers:
  - url: https://api.example.com/v2
    description: Production server

paths:
  /users:
    get:
      summary: List all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: per_page
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  meta:
                    $ref: '#/components/schemas/PaginationMeta'

    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: User created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '422':
          description: Validation error

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        created_at:
          type: string
          format: date-time

Documentación interactiva

# Serve Swagger UI for API documentation
npm install -g swagger-ui-express
# Visit: https://api.example.com/docs

Pruebas API

Pruebas de contrato

// Pact consumer-driven contract test
const { Pact } = require('@pact-foundation/pact');

describe('User API contract', () => {
  const provider = new Pact({
    consumer: 'Frontend App',
    provider: 'User Service',
    port: 1234,
  });

  beforeAll(() => provider.setup());

  describe('GET /users/123', () => {
    beforeAll(() => {
      return provider.addInteraction({
        state: 'User 123 exists',
        uponReceiving: 'a request for user 123',
        withRequest: {
          method: 'GET',
          path: '/users/123',
          headers: { Authorization: 'Bearer valid-token' }
        },
        willRespondWith: {
          status: 200,
          headers: { 'Content-Type': 'application/json' },
          body: {
            data: {
              id: '123',
              email: 'user@example.com',
              name: 'Test User'
            }
          }
        }
      });
    });

    it('returns the user', async () => {
      const response = await fetch('http://localhost:1234/users/123', {
        headers: { Authorization: 'Bearer valid-token' }
      });
      expect(response.status).toBe(200);
    });

    afterEach(() => provider.verify());
  });

  afterAll(() => provider.finalize());
});

GraphQL frente a REST

Aspecto DESCANSO GrafoQL
Obtención de datos Múltiples puntos finales, respuesta fija Punto final único, definido por el cliente
Exceso de recuperación Puede devolver más datos de los necesarios Solicita exactamente lo que necesitas
Subestimado Puede necesitar múltiples solicitudes (N+1) Solicitud única de datos anidados
Almacenamiento en caché Integrado (almacenamiento en caché HTTP, CDN) Personalizado (caché Apollo, retransmisión)
Herramientas Maduro (rizo, cartero, navegadores) Creciendo (GraphiQL, Apollo DevTools)
Curva de aprendizaje Bajo Medio
Versionamiento Versionado de URI Agregar campos, desaprobar los antiguos
Carga de archivos Nativo (multiparte) Requiere un manejo especial
En tiempo real WebSocket, SSE Suscripciones

Cuándo utilizar GraphQL

  • Requisitos de datos complejos y anidados (paneles de control, aplicaciones móviles).
  • Múltiples tipos de clientes (web, móviles, IoT) que necesitan datos diferentes.
  • Requisitos frontend en rápida evolución.

Cuándo utilizar DESCANSO

  • Operaciones CRUD simples.
  • API públicas con amplia base de consumidores.
  • El almacenamiento en caché es importante (CDN para puntos finales GET).
  • Carga de archivos y datos binarios.

Gestión del ciclo de vida de API

Estrategia de versiones

version_policy:
  - v1: Initial release (stable, deprecated)
  - v2: Current version (active development)
  - v3: Planned (future)

deprecation:
  notice_period: 6 months
  communication: email, API response header
  sunset_header: true  # Deprecation: true

transition:
  new_features: v2 only
  bug fixes: v1 critical only
  migrations: provide migration guide

Encabezados de obsolescencia

GET /v1/users/123
Sunset: Sat, 30 Nov 2024 00:00:00 GMT
Deprecation: true
Link: </v2/users/123>; rel="successor-version"

Conclusión

Un buen diseño de API sigue estos principios:

  1. Coherencia: los nombres, los formatos de error, la paginación y la autenticación deben seguir los mismos patrones.
  2. Experiencia de desarrollador: documentación clara, comportamiento predecible y mensajes de error útiles.
  3. Seguridad por defecto: HTTPS, autenticación, limitación de velocidad, validación de entrada, CORS.
  4. Compatibilidad con versiones anteriores: versione conscientemente, desapruebe con elegancia, comunique cambios importantes.
  5. Rendimiento: paginación, almacenamiento en caché de encabezados, campos dispersos, compresión.

Una API es un producto. Invierta en su diseño, documentelo minuciosamente, versione con cuidado y trate cada cambio importante con la seriedad que merece.

Artículos destacados

6 patrones de programación que aprendí de ingenieros senior
EditorialES
5 minGratis

6 patrones de programación que aprendí de ingenieros senior

TecnologíaCodeProgrammingCoding
0 aplausos
Leer
Crea un asistente privado de notas con IA
DestacadoES
9 minGratis

Crea un asistente privado de notas con IA

Convierte notas Markdown en un asistente consultable sin exponer borradores privados.

TecnologíaGuías de plataformaEscritura con IAAIAutomationData
0 aplausos
Leer
Automatiza la extracción de datos de facturas con OCR
EditorialES
8 minGratis

Automatiza la extracción de datos de facturas con OCR

Crea un flujo que lee facturas, extrae campos y marca valores dudosos para revisión.

TecnologíaArtículos de ingenieríaEscritura con IAAIAutomationData
0 aplausos
Leer

Comentarios

0 comentarios

Todavía no hay comentarios aprobados. Las respuestas nuevas pueden esperar moderación.