Los API Resources de Laravel son el estándar de facto para la transformación de respuestas API. Están bien documentados, tienen soporte oficial y son efectivos para operaciones CRUD sencillas. Sin embargo, a medida que aumenta la complejidad de la aplicación y evolucionan los patrones de consulta, las limitaciones de este enfoque se hacen evidentes.
Este artículo examina cuándo los API Resources son apropiados y cuándo los Data Transfer Objects (DTOs) proporcionan beneficios arquitectónicos superiores.
El caso estándar: API Resources son suficientes
Considera un endpoint CRUD típico de usuarios donde la respuesta refleja fielmente el modelo subyacente:
// app/Http/Resources/UserResource.php
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at->format('Y-m-d'),
];
}
}
// Controller
public function index()
{
$users = User::all();
return UserResource::collection($users);
}Beneficios: código mínimo, implementación rápida, reutilizable en múltiples endpoints.
El problema N+1: un error común
A medida que los requisitos se expanden para incluir datos relacionales, emerge un antipatrón común:
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'posts_count' => $this->posts->count(), // 🚨 N+1 query
'last_comment' => $this->comments()->latest()->first(), // 🚨 N+1 query
];
}
}Esta implementación dispara consultas adicionales para cada entidad. Con 50 usuarios, esto resulta en más de 100 consultas a la base de datos.
La solución canónica: eager loading en el controlador:
$users = User::with(['posts', 'comments'])->get();Sin embargo, este enfoque introduce un problema de separación de responsabilidades: el Resource accede libremente a las relaciones mientras la responsabilidad de optimizar las consultas reside en el controlador. Olvidar la cláusula with() resulta en degradación del rendimiento que puede no ser inmediatamente evidente.
Agregaciones complejas: donde los Resources muestran limitaciones
Considera un endpoint que requiere datos agregados de múltiples tablas:
$stats = DB::table('users')
->join('posts', 'users.id', '=', 'posts.user_id')
->join('comments', 'posts.id', '=', 'comments.post_id')
->selectRaw('
users.id,
users.name,
COUNT(DISTINCT posts.id) as posts_count,
COUNT(comments.id) as comments_count,
MAX(posts.created_at) as last_post_date
')
->groupBy('users.id', 'users.name')
->get();Esta consulta devuelve objetos stdClass en lugar de modelos Eloquent. Adaptar Resources para este caso de uso requiere compromisos:
class UserStatsResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id ?? null,
'name' => $this->name ?? null,
'posts_count' => $this->posts_count ?? 0,
'comments_count' => $this->comments_count ?? 0,
];
}
}Aunque funcional, este enfoque sacrifica la seguridad de tipos e introduce ambigüedad sobre las expectativas de estructura de datos.
DTOs: un enfoque alternativo
Los Data Transfer Objects proporcionan estructura explícita y desacoplan la obtención de datos de la lógica de transformación:
// app/DTOs/UserStatsDTO.php
class UserStatsDTO
{
public function __construct(
public int $id,
public string $name,
public int $postsCount,
public int $commentsCount,
public ?string $lastPostDate
) {}
public static function fromQueryResult(object $result): self
{
return new self(
id: $result->id,
name: $result->name,
postsCount: $result->posts_count,
commentsCount: $result->comments_count,
lastPostDate: $result->last_post_date
);
}
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'posts_count' => $this->postsCount,
'comments_count' => $this->commentsCount,
'last_post_date' => $this->lastPostDate,
];
}
}
// Controller
public function stats()
{
$results = DB::table('users')
->join('posts', 'users.id', '=', 'posts.user_id')
->selectRaw('...')
->get();
$dtos = $results->map(fn($r) => UserStatsDTO::fromQueryResult($r));
return response()->json($dtos->map->toArray());
}Ventajas: Seguridad de tipos completa con propiedades tipadas de PHP 8+, contratos explícitos de estructura de datos, eliminación de consultas N+1 accidentales, testabilidad independiente de la lógica de transformación, y reutilización en diferentes capas de la aplicación (jobs, eventos, comandos).
Análisis comparativo
API Resources: casos de uso óptimos
Operaciones CRUD simples, estructura de respuesta que refleja fielmente la estructura del modelo, equipos con fuerte conocimiento del framework Laravel, y aplicaciones de pequeña a mediana escala.
DTOs: escenarios preferidos
Consultas complejas con joins y agregaciones, composición de datos de múltiples fuentes, APIs críticas en rendimiento, requisitos estrictos de contratos, y necesidades de cobertura completa de tests.
Recomendación arquitectónica
En lugar de adoptar un enfoque monolítico, selecciona el patrón apropiado según los requisitos específicos de cada endpoint:
// CRUD simple → API Resource
public function show(User $user)
{
return new UserResource($user->load('profile'));
}
// Agregación compleja → DTO
public function dashboard()
{
$stats = $this->calculateDashboardStats();
return response()->json(
DashboardDTO::fromStats($stats)->toArray()
);
}Indicadores para adoptar DTOs: Uso de selectRaw() o DB::raw() en consultas, tres o más joins de tablas, divergencia significativa entre estructura de respuesta y modelo, problemas recurrentes de N+1, y requisitos para testear la lógica de transformación de forma aislada.
Conclusión
Aunque los API Resources representan el enfoque recomendado por el framework y funcionan efectivamente para casos de uso estándar, la filosofía de Laravel no obliga a su aplicación universal. Los DTOs ofrecen control superior y claridad arquitectónica en escenarios que involucran transformaciones de datos complejas.
El diseño óptimo de software prioriza soluciones específicas al problema sobre la adherencia dogmática a patrones. La elección entre Resources y DTOs debe estar guiada por las características específicas de cada endpoint en lugar de la práctica convencional.