Qué es una API REST y cómo diseñarla bien – RocPair

Qué es una API REST y cómo diseñarla bien

Qué es una API REST y cómo diseñarla bien

Compara estas dos formas de pedir lo mismo a un servidor:

Qué es una API REST y cómo diseñarla bien
Foto: NASA — Public domain, vía Wikimedia Commons
POST /api/obtenerPedidosDelCliente      {"idCliente": 42, "accion": "listar"}
GET  /api/v1/clientes/42/pedidos

Las dos pueden funcionar. Pero la segunda se entiende sin leer documentación, se puede guardar en caché, se puede probar desde el navegador y sigue las mismas reglas que miles de otras APIs. Esa es, en pocas palabras, la diferencia entre una API que solo funciona y una API REST bien diseñada.

Qué significa REST

REST (Representational State Transfer) es un estilo de arquitectura descrito por Roy Fielding en su tesis doctoral del año 2000. No es un protocolo ni una librería: es un conjunto de restricciones para diseñar sistemas que se comunican por HTTP. Las ideas centrales son cuatro:

  • Recursos. Todo lo que la API expone es un recurso con una dirección propia: un cliente, un pedido, una factura.
  • Representaciones. El cliente no recibe el recurso en sí, sino una representación de él, normalmente en JSON.
  • Interfaz uniforme. Las operaciones se expresan con los métodos estándar de HTTP, no con verbos inventados en la URL.
  • Sin estado. Cada petición lleva toda la información necesaria para procesarla. El servidor no recuerda "en qué paso iba" el cliente entre una petición y otra.

En la práctica, muy pocas APIs cumplen todas las restricciones de la tesis original. Cuando la gente dice "API REST" suele referirse a una API HTTP que organiza sus URLs alrededor de recursos y usa bien los métodos y códigos de estado. Eso es lo que vamos a diseñar aquí.

Diseña las URLs alrededor de sustantivos

La URL identifica qué recurso quieres; el método HTTP dice qué quieres hacer con él. Si tus rutas contienen verbos, estás mezclando las dos cosas.

EvitaPrefiere
GET /obtenerUsuariosGET /usuarios
POST /crearUsuarioPOST /usuarios
GET /usuario?id=42GET /usuarios/42
POST /borrarUsuario/42DELETE /usuarios/42
GET /usuarios/42/obtenerPedidosGET /usuarios/42/pedidos

Unas reglas que ayudan a mantener la coherencia:

  • Usa sustantivos en plural para las colecciones: /productos, /facturas.
  • Usa minúsculas y separa palabras con guiones: /metodos-de-pago, no /MetodosDePago.
  • Anida como máximo un nivel cuando la relación es de pertenencia clara: /clientes/42/pedidos. Más niveles vuelven las URLs frágiles; para casos complejos, usa filtros: /pedidos?cliente=42.
  • Para acciones que no encajan en el modelo de recursos, como "enviar una factura por correo", es aceptable una subruta con sustantivo: POST /facturas/15/envios.

Usa cada método para lo que es

MétodoUso¿Idempotente?Ejemplo
GETLeer uno o varios recursosSíGET /pedidos/7
POSTCrear un recurso en una colecciónNoPOST /pedidos
PUTReemplazar un recurso completoSíPUT /pedidos/7
PATCHModificar algunos camposNo necesariamentePATCH /pedidos/7
DELETEEliminar un recursoSíDELETE /pedidos/7

Idempotente significa que repetir la misma petición produce el mismo estado final. Borrar el pedido 7 dos veces deja el sistema igual que borrarlo una vez. Crear un pedido con POST dos veces, en cambio, crea dos pedidos. Esto importa cuando la red falla: un cliente puede reintentar un PUT o un DELETE con tranquilidad, pero reintentar un POST puede duplicar datos.

Para resolver ese problema en operaciones sensibles, como pagos, varias APIs aceptan una cabecera Idempotency-Key: el cliente genera un identificador único por operación y el servidor, si recibe la misma clave dos veces, devuelve la respuesta original en lugar de repetir la acción.

Y una regla sin excepciones: un GET nunca debe modificar datos. Los navegadores, los rastreadores y los sistemas de caché asumen que pueden repetir peticiones GET sin consecuencias.

Responde con el código de estado correcto

Devolver siempre 200 OK con un campo "error": true en el cuerpo obliga a cada cliente a revisar el contenido para saber si algo salió mal. Los códigos de estado existen para eso.

CódigoCuándo usarlo
200 OKLectura o actualización exitosa con cuerpo de respuesta
201 CreatedRecurso creado; incluye la cabecera Location con su URL
204 No ContentÉxito sin cuerpo, típico de un DELETE
400 Bad RequestLa petición está mal formada: JSON inválido, parámetro con tipo incorrecto
401 UnauthorizedFalta autenticación o el token no es válido
403 ForbiddenEl usuario está autenticado pero no tiene permiso
404 Not FoundEl recurso no existe
409 ConflictChoca con el estado actual: email ya registrado, versión desactualizada
422 Unprocessable ContentEl formato es correcto pero los datos no pasan las reglas de negocio
429 Too Many RequestsEl cliente superó el límite de peticiones
500 Internal Server ErrorFallo inesperado del servidor

La frontera entre 400 y 422 genera debates. Lo importante es elegir un criterio, documentarlo y aplicarlo igual en toda la API.

Errores que el cliente pueda entender

Un buen mensaje de error dice qué pasó, por qué y, si es posible, cómo corregirlo. Existe un estándar para esto: el formato Problem Details, definido en el RFC 9457, que se sirve con el tipo application/problem+json:

{
  "type": "https://api.ejemplo.com/errores/validacion",
  "title": "Los datos enviados no son válidos",
  "status": 422,
  "detail": "Hay 2 campos con errores",
  "instance": "/pedidos",
  "errores": [
    { "campo": "email", "mensaje": "No tiene un formato válido" },
    { "campo": "cantidad", "mensaje": "Debe ser mayor que cero" }
  ]
}

El estándar permite agregar campos propios, como errores en el ejemplo. Lo que nunca debe aparecer en una respuesta de error es información interna: trazas de excepción, consultas SQL o rutas del servidor. Regístralas en tus logs, no se las muestres al cliente.

Paginación, filtros y orden

Ninguna colección debería devolver todos sus elementos de una vez. Hay dos estrategias principales de paginación:

Por desplazamiento (?limite=20&desplazamiento=40). Es fácil de implementar y permite saltar a cualquier página. Su problema: con tablas grandes, los desplazamientos altos son lentos, y si se insertan elementos mientras el usuario navega, puede ver duplicados o saltarse algunos.

Por cursor (?limite=20&despues=eyJpZCI6MTQwfQ). El servidor devuelve un cursor opaco que marca dónde terminó la página, y el cliente lo envía para pedir la siguiente. Es estable y eficiente, aunque no permite saltar a la página 37 directamente. Es la opción preferida para listas largas o que cambian a menudo.

Una respuesta paginada clara incluye los datos y la información para continuar:

{
  "datos": [ { "id": 141, "total": "125000.00" } ],
  "paginacion": {
    "limite": 20,
    "siguiente": "/pedidos?limite=20&despues=eyJpZCI6MTYwfQ"
  }
}

Para filtrar y ordenar, usa parámetros de consulta con nombres predecibles: /pedidos?estado=pagado&desde=2025-01-01&orden=-creado_en, donde el signo menos indica orden descendente. Documenta qué campos admiten filtro y valida los valores: aceptar cualquier nombre de columna en orden y pasarlo directo a SQL es una puerta abierta a inyecciones.

Formato de los datos

  • Elige una convención de nombres y no la mezcles. creadoEn o creado_en, pero no ambas en la misma API.
  • Fechas en ISO 8601 con zona horaria, preferiblemente en UTC: 2025-06-30T14:05:00Z.
  • Dinero sin decimales flotantes. Envía los montos como texto ("125000.00") o en la unidad mínima de la moneda como entero, y acompáñalos del código de moneda.
  • Identificadores como texto si pueden superar el rango de enteros seguros de JavaScript, que llega a unos 9.000 billones (2 elevado a 53).

Versiona desde el primer día

Tarde o temprano necesitarás un cambio que rompa la compatibilidad: renombrar un campo, cambiar un tipo, eliminar un recurso. La forma más sencilla de manejarlo es incluir la versión en la URL (/v1/pedidos) y mantener la versión anterior durante un periodo anunciado. Otras APIs usan cabeceras para versionar; es más elegante, pero más difícil de probar y depurar. Para la mayoría de proyectos, la versión en la URL es suficiente.

Los cambios que no rompen nada, como agregar un campo nuevo a una respuesta, no necesitan nueva versión. Por eso los clientes deben ignorar los campos que no conocen.

Seguridad básica que no es opcional

  • Sirve la API únicamente por HTTPS.
  • Autentica cada petición; no confíes en que "nadie conoce la URL".
  • Verifica permisos sobre cada recurso, no solo sobre la ruta. Que un usuario pueda ver /pedidos/7 no significa que pueda ver /pedidos/8. Este fallo, el acceso a objetos de otros usuarios cambiando un identificador, está entre los más frecuentes en las listas de riesgos de OWASP para APIs.
  • Aplica límites de peticiones y responde 429 cuando se superen.
  • Valida todo lo que entra: tipos, longitudes, rangos.

Documenta con OpenAPI

La especificación OpenAPI permite describir tu API en un archivo YAML o JSON: rutas, parámetros, cuerpos y respuestas. A partir de ese archivo hay herramientas que generan documentación navegable, clientes en varios lenguajes y pruebas. Un fragmento mínimo se ve así:

openapi: 3.1.0
info:
  title: API de pedidos
  version: 1.0.0
paths:
  /pedidos/{id}:
    get:
      summary: Obtiene un pedido
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: El pedido solicitado
        "404":
          description: El pedido no existe

Un ejercicio para cerrar

Toma una API que conozcas, propia o de un proyecto en el que trabajes, y revísala con tres preguntas: ¿las URLs contienen verbos?, ¿los errores usan códigos de estado distintos de 200?, ¿las colecciones están paginadas? Si alguna respuesta es "no", ya tienes por dónde empezar a mejorarla, y la próxima API que diseñes desde cero va a nacer con esas decisiones tomadas.

Si quieres conocer otros artículos parecidos a Qué es una API REST y cómo diseñarla bien puedes visitar la categoría Programacion.

Contenido que te pude interesar

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir