Empecemos con algo simple: un endpoint de health check. Ahí está, diciéndote que la API está viva y en forma. Puede sonar trivial, pero es aquí donde establecemos los patrones que usarás en todos los endpoints que vengan después. Mantenlo simple, mantenlo consistente, y tendrás un plano para todo lo demás.
// routes/api.php
Route::get('/health', HealthController::class);
Un health check debería ser señal pura: rápido, ligero y de coste cero. Sólo compruebas que los servicios críticos están conectados —base de datos, capa de caché y sistema de colas. Sin consultas profundas. Sin llamadas a APIs externas. Sin efectos secundarios. Sólo un 200 limpio cuando todo va bien, o un 503 cuando algo está roto. Tus sistemas de monitorización pingan este endpoint cada cinco o diez segundos y conocen al instante el estado de tu API sin añadir carga. Ese es todo el propósito, nada más.
Este es un endpoint público: sin autenticación, sin middleware (por ahora). Cualquiera puede llamarlo, sin preguntas. Lo pingan tus balanceadores. Lo pingan tus sistemas de monitorización. Lo pinga tu pipeline de despliegue. Todos comprueban lo mismo: ¿está viva tu API y responden sus servicios?
{
"data": {
"status": "ok",
"timestamp": "2026-02-11T21:50:47+00:00",
"services": {
"database": "connected",
"cache": "connected",
"queue": "active"
}
}
}
La forma rápida
// app/Http/Controllers/HealthController.php
final readonly class HealthController
{
public function __invoke(): Response
{
return response()->json([
'data' => [
'status' => 'ok',
'timestamp' => now()->toIso8601String(),
'services' => [
'database' => 'connected',
'cache' => 'connected',
'queue' => 'active',
],
]
]);
}
}
Primero, piensa en lo que estamos haciendo.
Estamos atendiendo una petición HTTP. Necesitamos comprobar que los servicios críticos funcionan. Necesitamos devolver una respuesta. Son tres pasos.
A medida que tu API crece, querrás asegurar consistencia, tipado más fuerte, códigos de error con significado que describan claramente qué pasó, localización —sea un 200 OK o un 503 Service Unavailable— y pruebas exhaustivas que den confianza en tu implementación.
Es entonces cuando la forma rápida empieza a sentirse frágil. Estás copiando y pegando el formato de respuesta entre endpoints. La lógica de validación está dispersa. Tus controladores crecen a más de cien líneas sin separación clara entre lo HTTP y lo de negocio.
Probar se vuelve tedioso porque haces aserciones contra arrays de forma desconocida. Las propiedades y los tipos no están claros hasta que lees la implementación. Estos no son problemas del enfoque rápido en sí. No está mal. Simplemente ya no es suficiente. Dicho de otro modo: no es escalable.
La forma escalable
// app/Http/Controllers/HealthController.php
final readonly class HealthController
{
public function __invoke(): Responsable
{
return new ModelResponse(
data: new HealthResource(
resource: HealthDTOMapper::toDTO(...),
)
);
}
}
La diferencia es el uso de Response Wrappers mediante respuestas Responsable, resources, DTOs y mappers (o transformadores, como se les llama a veces). No hay magia. En lugar de devolver respuestas en crudo desde los controladores, este enfoque introduce una capa estructurada: las respuestas implementan un contrato común, los datos se envuelven en objetos de respuesta consistentes, la salida se transforma mediante resources y los valores en crudo se mapean a DTOs tipados. El resultado es una API predecible, testable y segura en tipos, donde formato, estructura, transformación y localización están claramente separados de la lógica del controlador.
No son ejercicios académicos. Son soluciones prácticas refinadas con experiencia real en producción. Tus controladores se mantienen finos y enfocados. Quien consume tu API escribe una vez y reutiliza siempre. Alguien nuevo mira un endpoint y los entiende todos. De eso hablaremos a lo largo del libro, porque eso es lo que separa un prototipo rápido de una API real en producción.