Proyecto compartido. En el taller de Intermodular que abre esta semana has trabajado el presupuesto de calidad. En Servidor continúas la implementación del mismo producto.
Se explica
25 minutos · explicación y demostración
El CRUD ya se puede comprobar. Hoy revisarás cómo nombra sus recursos y utiliza HTTP. REST es un estilo de diseño; empezaremos por una consecuencia práctica: que las rutas describan recursos y que el método HTTP exprese la operación.
Tu API funciona. Eso no la hace REST
En la sesión 8 entregaste dieciséis endpoints con sus códigos correctos y una colección que los verifica. El resultado funciona, es comprobable y resulta utilizable por otra persona.
Aun así, ante la pregunta «¿es una API REST?» en una entrevista, la respuesta honesta hoy sería: en parte, y no sabría decir en qué parte.
Eso es lo que arreglamos esta semana. No porque la palabra sea importante, sino porque detrás de ella hay un conjunto de decisiones de diseño que hacen que una API se pueda usar sin manual, crecer sin romperse y entender sin preguntar.
El malentendido más extendido del sector
«Mi API devuelve JSON, luego es REST.» No. El formato no tiene nada que ver: se puede hacer una API REST que devuelva XML y una API terrible que devuelva JSON.
Lo que decide no es qué formato usas, sino cómo organizas lo que hay detrás de las URLs y qué significan tus métodos y tus códigos.
Qué es REST
REST
Representational State Transfer. Un estilo de arquitectura descrito por Roy Fielding en el año 2000, en la tesis donde analizaba por qué la web había funcionado a escala planetaria cuando casi ningún sistema distribuido lo consigue.
Fíjate en «estilo». No es un protocolo, no es un estándar que se cumpla o se incumpla, y no es una librería que se instala. Es un conjunto de restricciones que, si las aceptas, te dan unas propiedades a cambio.
Las que importan aquí son cinco:
| Restricción | Qué significa | ¿Ya la cumples? |
|---|---|---|
| Cliente-servidor | Quien pide y quien responde son programas separados que solo se comunican por el contrato | Sí, desde la UD1 |
| Sin estado | Cada petición trae todo lo necesario; el servidor no recuerda la anterior | Sí, aunque sin saberlo |
| Cacheable | La respuesta puede declarar si se puede reutilizar | No, todavía |
| Interfaz uniforme | Todos los recursos se manipulan igual, con las mismas reglas | Parcialmente |
| Sistema por capas | Puede haber intermediarios sin que el cliente se entere | Sí, gratis |
Tres las cumples sin haber hecho nada: te las regaló HTTP. La cuarta es el trabajo de esta unidad.
La interfaz uniforme, que es la que cuesta
Es la restricción central y se apoya en tres ideas:
- Cada cosa tiene su dirección. Un recurso se identifica por una URL, y siempre la misma
- Se manipula a través de representaciones. No mandas el objeto: mandas una descripción de cómo debe quedar
- Los mensajes se explican solos. El método, el código y las cabeceras dicen qué ocurre sin necesitar documentación aparte
La segunda idea es la de la sesión 10 y la que más cuesta al principio. La primera y la tercera son las de esta semana.
Un recurso es un sustantivo
Recurso
Cualquier cosa de la que tu API pueda hablar y a la que se pueda dar una dirección: un proyecto, una tarea, un usuario, un comentario. También una colección de ellas.
Esa es toda la definición, y la consecuencia práctica es enorme: si la URL contiene un verbo, no nombra un recurso, sino que emite una orden. Las órdenes no se pueden identificar, ni cachear, ni relacionar entre sí.
Pensar en acciones
Cada funcionalidad nueva inventa una ruta nueva. La API crece como una lista de órdenes que hay que memorizar, y solo la conoce quien la escribió.
Pensar en recursos
Las cosas del dominio son pocas y estables. Sobre cada una se aplican siempre los mismos cinco métodos, así que quien conoce una sabe usar las demás.
Esa es la ganancia real, y no es estética: una API de nivel 2 con veinte recursos se aprende una sola vez, mientras que una de nivel 1 con veinte recursos son cien rutas distintas que hay que consultar.
Las siete reglas de nombrado
| # | Regla | Mal | Bien |
|---|---|---|---|
| 1 | Sustantivos, nunca verbos | /crearTarea |
POST /tareas |
| 2 | Colecciones en plural | /tarea/3 |
/tareas/3 |
| 3 | Minúsculas y guiones | /ordenesDeTrabajo |
/ordenes-de-trabajo |
| 4 | Sin extensión de archivo | /tareas.json |
/tareas |
| 5 | Jerarquía para la pertenencia | /tareas?proyecto=7 |
/proyectos/7/tareas |
| 6 | Query string para filtrar | /tareas/completadas |
/tareas?completada=true |
| 7 | Sin barra final | /tareas/ |
/tareas |
Las dos que de verdad se piensan son la 5 y la 6, porque son la misma decisión de la UD1 vista desde arriba.
Jerarquía o filtro
La pregunta que lo resuelve
¿El recurso existe por sí solo, o solo tiene sentido dentro de otro?
Una tarea existe por sí sola: tiene su id y se puede consultar directamente en /tareas/41. Que además pertenezca a un proyecto es una relación, y /proyectos/7/tareas es una forma cómoda de recorrerla.
Un comentario de una incidencia, en cambio, no significa nada fuera de ella. Ahí la jerarquía constituye la única estructura con sentido, y no una comodidad de diseño.
Existe además un límite práctico:
/proyectos/7/tareas/41/comentarios/5/respuestas/2
Nadie escribe eso, nadie lo lee y nadie lo mantiene. Dos niveles de profundidad es el máximo razonable. Si un recurso tiene id propio, se accede directo:
/comentarios/5/respuestas
Se trabaja
140 minutos · implementación guiada sobre el proyecto propio
Paso 1 · Retomar el proyecto y preparar la comprobación
- Ejecuta la colección de la sesión 8 antes de cambiar ninguna URL. Abre los controladores y la tabla del contrato.
- Haz un inventario de método, ruta, parámetros y significado de cada operación. Incluye las operaciones de negocio que no equivalen a crear o borrar un registro.
- Elige una ruta mejorable y escribe su propuesta antes de editar. Anota qué peticiones y qué consumidor tendrían que actualizarse.
Paso 2 · Comparar los niveles del modelo de madurez de Richardson
Hay una forma muy práctica de situar cualquier API, y sirve tanto para juzgar la tuya como para entender la de otro. Son cuatro niveles, cada uno construido sobre el anterior.
- Nivel 0 · Una sola puerta. Una URL, un método, y dentro del cuerpo se dice qué se quiere hacer
- Nivel 1 · Recursos. Cada cosa tiene su propia URL
- Nivel 2 · Verbos y códigos. El método HTTP dice la acción y el código dice el resultado
- Nivel 3 · Hipermedia. Las respuestas incluyen los enlaces a lo que se puede hacer después
Míralos con ejemplos, porque así se reconocen de un vistazo:
Nivel 0 · una sola puerta
POST /api
{ "accion": "obtenerTarea", "id": 3 }
POST /api
{ "accion": "borrarTarea", "id": 3 }
HTTP se usa solo como sobre. Todo pasa por una URL y un método, y la intención va escondida en el cuerpo. Ni las URLs ni los códigos significan nada.
Nivel 1 · recursos con nombre
POST /tareas/obtener
POST /tareas/borrar
POST /tareas/3/marcar-completada
Existen ya URLs distintas para recursos distintos, lo que constituye un avance real, si bien la acción permanece en la ruta y toda operación emplea POST.
Reconoce esto, porque es exactamente lo que escribe todo el mundo la primera vez, y es lo que la UD1 te prohibió sin explicarte del todo por qué.
Nivel 2 · el método y el código hacen su trabajo
GET /tareas/3 → 200
DELETE /tareas/3 → 204
POST /tareas → 201
PATCH /tareas/3 → 200
La ruta dice sobre qué, el método dice qué, y el código dice cómo ha ido. Aquí es donde vive la inmensa mayoría de las APIs profesionales, y donde debe estar la tuya al terminar la unidad.
Nivel 3 · la respuesta te dice qué puedes hacer ahora
{
"id": 3,
"titulo": "Revisar el login",
"estado": "abierta",
"_links": {
"self": { "href": "/tareas/3" },
"cerrar": { "href": "/tareas/3/cierre", "method": "POST" },
"proyecto":{ "href": "/proyectos/7" }
}
}
El cliente no necesita saberse las rutas: las va descubriendo en las respuestas, igual que tú navegas por una web siguiendo enlaces sin conocer sus URLs.
Honestidad sobre el nivel 3
Es el nivel que Fielding considera imprescindible para llamar «REST» a una API, y a la vez es el que casi nadie implementa. Añade complejidad y muy pocos clientes la aprovechan.
En este módulo llegaremos al nivel 2 y lo haremos bien. Del 3 tienes que saber que existe, por qué existe y por qué se decide no usarlo: eso es exactamente lo que se espera de un profesional junior.
Paso 3 · Dónde está tu API ahora mismo
Cógela y sitúala. Esta es la rúbrica:
| # | Criterio | Sí / No |
|---|---|---|
| 1 | Ninguna ruta contiene un verbo | |
| 2 | Cada recurso tiene una URL propia y estable | |
| 3 | Las colecciones se nombran en plural | |
| 4 | La acción la expresa siempre el método HTTP | |
| 5 | GET nunca modifica nada |
|
| 6 | Cada final posible tiene su código de estado | |
| 7 | Los recursos relacionados se expresan con jerarquía en la ruta | |
| 8 | El mismo tipo de dato se representa igual en todos los endpoints | |
| 9 | La API no publica campos internos del modelo | |
| 10 | Las respuestas incluyen enlaces a operaciones relacionadas |
- Del 1 al 6
- Deberías tenerlos ya. La UD1 y la UD2 te los impusieron como reglas sueltas, sin decirte que juntas formaban el nivel 2.
- El 7 y el 8
- Es donde se decide la sesión 9. Probablemente tengas el 7 a medias, con la ruta anidada de tareas de un proyecto, y el 8 sin comprobar nunca.
- El 9
- Casi seguro que es un no, y es el tema de la sesión 10. Tu API publica exactamente los campos que tenga la clase, sin que nadie lo haya decidido.
- El 10
- Es un no, y va a seguir siéndolo. Es el nivel 3, y ya sabes por qué no vamos.
Paso 4 · Comparar tres diseños de rutas para las mismas operaciones
Aquí tienes la misma funcionalidad —consultar una incidencia, cerrarla y listar las de un proyecto— escrita en tres niveles.
POST /servicio
{ "op": "getIncidencia", "id": 41 }
POST /servicio
{ "op": "cerrarIncidencia", "id": 41 }
POST /servicio
{ "op": "listarIncidencias", "proyecto": 7 }
POST /incidencia/get/41
POST /incidencia/cerrar/41
POST /incidencias/listar/7
GET /incidencias/41
PATCH /incidencias/41 { "estado": "cerrada" }
GET /proyectos/7/incidencias
Responde por escrito, comparando las tres columnas:
- En el nivel 0, ¿puede un intermediario —una caché, un cortafuegos— saber si una petición modifica datos? ¿Y en el nivel 2?
- En el nivel 1, ¿qué pasa si el navegador reintenta una petición que se quedó sin respuesta?
- ¿Cuántas rutas nuevas hace falta inventar en cada nivel para añadir «reabrir una incidencia»?
- Un desarrollador nuevo llega al equipo. ¿En cuál de los tres puede adivinar cómo se borra una incidencia sin preguntar?
La pregunta 4 es la que resume la unidad. Una API bien diseñada es la que se puede adivinar.
Paso 5 · Audita tu propia API
- Pasa la rúbrica de diez criterios a tu API de la UD2, endpoint por endpoint. Sé duro: un «a medias» es un no.
- Sitúa tu API en el mapa de niveles y justifica la posición en tres frases.
- Haz una lista de todo lo que incumples, ordenada por lo que costaría arreglarlo, de más barato a más caro.
- Para los tres más baratos, escribe exactamente qué cambiarías.
Guarda esta auditoría: en la sesión 14 volverás a pasarla y la diferencia entre las dos es parte de la entrega de la unidad.
Recursos y REST
Paso 6 · Lo que no es un CRUD
Aquí está la parte difícil y la que separa una API pensada de una API copiada. ¿Qué haces con «archivar un proyecto», «cerrar una incidencia», «enviar un aviso» o «iniciar sesión»?
Ninguna consiste en crear, leer, actualizar ni borrar, y sin embargo deben exponerse. Hay tres estrategias, en este orden de preferencia:
La mayoría de las «acciones» son en realidad un campo que cambia de valor.
PATCH /incidencias/41
{ "estado": "cerrada" }
«Cerrar» consiste en asignar el valor cerrada al estado, y no constituye una operación independiente. Si el dominio ya tiene ese campo, no hace falta inventar nada.
A veces la acción esconde una cosa que merece existir por sí misma.
POST /incidencias/41/comentarios
POST /proyectos/7/miembros
«Comentar» equivale a crear un comentario, y no a un verbo que se cuelga de la incidencia. En cuanto lo ves así, aparece una colección que además se puede listar, paginar y borrar.
Cuando lo anterior no encaja, se expone la acción como un recurso propio y se documenta:
POST /incidencias/41/cierre
POST /pedidos/12/reembolso
POST /sesiones
Conviene observar que siguen siendo sustantivos —el cierre, el reembolso, la sesión— y que se ejecutan con POST, por no ser idempotentes.
Cuándo está bien salirse de la norma
La estrategia 3 no es una derrota. Hay operaciones —un pago, un envío de correo, un proceso largo— que no son el cambio de un campo y forzarlas a serlo produce una API peor y más confusa.
Lo que no vale es usarla por defecto porque es la más fácil. La regla: intenta la 1, luego la 2, y solo entonces la 3 — y cuando uses la 3, escribe por qué.
El caso del login, que todo el mundo pregunta
Iniciar sesión no es CRUD por ninguna parte, y aun así encaja en la estrategia 2 si lo miras bien: lo que se crea es una sesión.
POST /sesiones crea una, DELETE /sesiones/actual la cierra. Verás también POST /auth/login, que es la estrategia 3, y es perfectamente común.
Lo trabajaremos de verdad en la UD9. Hoy solo interesa que veas que hasta el caso más raro tiene un sustantivo detrás si lo buscas.
Paso 7 · Proponer métodos y rutas coherentes para doce operaciones
Reescribe cada una al nivel 2. Indica método y ruta, y en las que lo necesiten, qué va en el cuerpo.
| # | Ruta original | Qué hace |
|---|---|---|
| 1 | POST /crearProyecto |
Crea un proyecto |
| 2 | GET /obtenerProyecto?id=7 |
Devuelve el proyecto 7 |
| 3 | GET /borrarProyecto/7 |
Borra el proyecto 7 |
| 4 | POST /proyecto/7/editar |
Cambia el nombre del proyecto 7 |
| 5 | GET /listadoDeTareas |
Todas las tareas |
| 6 | GET /tareasDelProyecto/7 |
Las tareas del proyecto 7 |
| 7 | GET /tareas/pendientes |
Las tareas no completadas |
| 8 | POST /tarea/41/marcarCompletada |
Marca la tarea 41 como completada |
| 9 | POST /tarea/41/asignarUsuario/3 |
Asigna la tarea 41 al usuario 3 |
| 10 | POST /añadirComentario |
Añade un comentario a una incidencia |
| 11 | GET /proyectos.json |
Todos los proyectos |
| 12 | POST /archivarProyectosCerrados |
Archiva todos los proyectos cerrados |
Tres avisos, para que no las despaches en cinco minutos:
- La 3 tiene un problema mucho más grave que el nombre. Ya sabes cuál desde la UD1.
- La 9 admite al menos dos soluciones buenas y distintas. Escribe las dos y elige una argumentando.
- La 12 no encaja limpiamente en ninguna estrategia. Es a propósito: resuélvela como puedas y explica qué te chirría.
Paso 8 · El contrato de recursos del gestor
Usa la tabla como formato de documentación, no como obligación de añadir cinco entidades nuevas hoy. Sustituye sus filas por las entidades de tu propuesta y rellena colección, detalle y relaciones reales. Para cada ruta escribe un ejemplo con un id concreto y otro con un filtro. Señala qué rutas ya funcionan y cuáles están previstas: una ruta documentada como futura todavía no debe aparecer como superada en la colección.
| Recurso | Colección | Elemento | Relaciones |
|---|---|---|---|
| Proyecto | /proyectos |
/proyectos/{id} |
/proyectos/{id}/tareas |
| Tarea | /tareas |
/tareas/{id} |
— |
| Usuario | |||
| Comentario | |||
| Etiqueta |
Para cada recurso, decide además:
- Qué métodos acepta la colección y qué métodos acepta el elemento. No todos tienen que aceptar los cinco: un recurso que no se borra nunca no expone
DELETE, y decirlo es diseñar. - Qué filtros admite la colección, en query string.
- Si alguna relación merece ruta anidada o basta con un filtro.
Estoy atascado · las etiquetas
Una etiqueta es un recurso propio: existe aunque ninguna tarea la use, se lista y se borra. Hasta ahí, fácil.
Lo difícil es la relación: una tarea tiene varias etiquetas y una etiqueta está en varias tareas. Piensa qué URL representa «las etiquetas de la tarea 41», y qué método usarías para añadirle una que ya existe. Ojo: añadir una etiqueta existente a una tarea no crea una etiqueta nueva.
Paso 9 · Aplica el contrato a tu código
Haz el cambio ruta por ruta: modifica la anotación del controlador, reinicia, actualiza la petición guardada y comprueba su resultado. Para una colección anidada como comentarios de una tarea, crea el modelo con id, texto e id de su recurso padre; el GET filtra por el id de la ruta y el POST asigna esa referencia desde la ruta. Rechaza el padre inexistente antes de guardar. Si tu dominio ya tiene una relación equivalente, aplica el procedimiento a esa colección en lugar de crear Comentario por copiar el ejemplo.
- Renombra en tu proyecto todas las rutas que incumplan alguna de las siete reglas.
- Actualiza la colección de Postman para que siga en verde con las rutas nuevas. Si sacaste el servidor a
{{baseUrl}}, esto es rápido; si no, ya sabes por qué se hacía. - Añade el recurso
Comentarioa la API, anidado donde corresponda, con al menos listar y crear. - Anota en las decisiones técnicas qué rutas cambiaron y por qué.
Paso 10 · Comprobar y registrar el resultado del proyecto
- Compara el inventario anterior con el nuevo y justifica cada cambio por su significado, sin basarte solo en preferencias de nombres.
- Actualiza y ejecuta la colección con las nuevas rutas. Si Intermodular ya utiliza alguna, registra y coordina el cambio de contrato con ese cliente.
Ampliación si has completado el trabajo
Primero termina y verifica los pasos anteriores. Estos retos profundizan en el mismo contenido; no sustituyen la entrega ni obligan a iniciar otro proyecto.
Reto · Diagnostica una API ajena
Estas son rutas reales de una API interna de una empresa ficticia:
POST /api/getUsuarios
POST /api/getUsuarioById
POST /api/user/create
GET /api/borrarUsuario?id=12
POST /api/usuarios/12/update
GET /api/usuario/12/pedidos/listado
POST /api/actualizarEstadoPedido
GET /api/pedidos?borrarCancelados=true
- Sitúa esta API en el mapa y justifícalo.
- Señala la ruta más peligrosa de todas y explica qué puede llegar a ocurrir con ella un día cualquiera. Hay dos candidatas y una es claramente peor.
- Reescribe las ocho como nivel 2. Alguna se convertirá en la misma ruta que otra: dilo y explica por qué eso es bueno.
- Cuenta cuántas rutas quedan al final y explica en una frase a qué se debe la diferencia.
Ver respuestas
1 · Porque REST no dice nada del formato: habla de cómo se identifican los recursos, cómo se manipulan y qué significan los mensajes. Se puede devolver JSON con un diseño de nivel 0.
2 · Cliente-servidor, sin estado y sistema por capas. Las tres vienen dadas por el propio protocolo.
3 · En el nivel 1 cada cosa ya tiene su URL, pero la acción sigue metida en la ruta y todo se hace con el mismo método. En el nivel 2 la acción la expresa el método HTTP y el resultado lo expresa el código de estado.
4 · Porque añade complejidad al servidor y muy pocos clientes aprovechan los enlaces: la mayoría se escriben conociendo las rutas de antemano. Es una decisión de coste y beneficio, no un olvido.
Reto · La prueba de que se puede adivinar
Este es el examen real de una API bien nombrada.
- Dale a un compañero solo tres rutas de tu API, ninguna de comentarios.
- Pídele que escriba, sin verte y sin preguntarte: cómo listaría los comentarios de una incidencia, cómo crearía uno, cómo borraría uno, y cómo listaría solo los de un autor.
- Compara sus cuatro respuestas con tus rutas reales.
- Cada acierto es una regla que tu API cumple. Cada fallo es una decisión tuya que no era adivinable: anótala y decide si el nombre malo es el suyo o el tuyo.
Hazlo también al revés, con la API de él.
Ver respuestas
1 · Porque la URL identifica una cosa y la acción ya la expresa el método HTTP. Con el verbo en la ruta, cada funcionalidad nueva inventa una dirección nueva y la API deja de ser adivinable.
2 · Anidada cuando el recurso pertenece a otro o solo tiene sentido dentro de él; filtro cuando se trata de acotar una colección que existe por sí sola.
3 · Tratarlo como un cambio de estado con PATCH; descubrir que hay un recurso nuevo que crear; y, si nada de eso encaja, exponer la acción como un sustantivo propio con POST y documentar por qué.
4 · Que se aprende una vez. Sobre cada recurso se aplican siempre los mismos métodos, así que quien sabe usar uno sabe usar los demás sin consultar nada.
Cierre
15 minutos · resultado comprobable y explicación individual
Al terminar la sesión:
Cada ruta tiene un recurso o una operación de negocio justificable; la colección sigue funcionando.
Cada integrante explica una decisión del código apoyándose en una de las comprobaciones realizadas.