10. API REST y JSON en Laravel
API REST y JSON en Laravel
Section titled “API REST y JSON en Laravel”Laravel proporciona herramientas poderosas para crear APIs RESTful de forma rápida y sencilla. En esta guía, exploraremos cómo configurar rutas API, crear controladores específicos, formatear respuestas JSON y autenticar usuarios mediante tokens.
Rutas API y controladores tipo apiResource
Section titled “Rutas API y controladores tipo apiResource”Laravel facilita la creación de rutas API mediante el prefijo api y el método apiResource que genera automáticamente las rutas necesarias para un recurso RESTful.
Configuración básica
Section titled “Configuración básica”Las rutas API se definen en el archivo routes/api.php y automáticamente se les aplica el prefijo /api y el grupo de middleware api.
<?php
use AppHttpControllersAPIProductController;use IlluminateSupportFacadesRoute;
// Ruta API básicaRoute::get('/products', [ProductController::class, 'index']);
// Grupo de rutas con prefijo y middleware adicionalRoute::prefix('v1')->middleware('auth:sanctum')->group(function () { Route::get('/user', function () { return auth()->user(); });});Recursos API
Section titled “Recursos API”El método apiResource crea automáticamente las rutas necesarias para un controlador de recursos API, excluyendo las rutas create y edit que son específicas para aplicaciones web con formularios HTML.
// Definir un recurso API completoRoute::apiResource('products', ProductController::class);
// Definir múltiples recursos APIRoute::apiResources([ 'products' => ProductController::class, 'categories' => CategoryController::class,]);
// Recurso API con rutas anidadasRoute::apiResource('products.reviews', ProductReviewController::class);Esto genera las siguientes rutas:
+--------+-----------+----------------------------+---------------+----------------------------------------------+| Method | URI | Name | Action | Controller |+--------+-----------+----------------------------+---------------+----------------------------------------------+| GET | /products | products.index | index | AppHttpControllersAPIProductController || POST | /products | products.store | store | AppHttpControllersAPIProductController || GET | /products/{product} | products.show | show | AppHttpControllersAPIProductController || PUT | /products/{product} | products.update | update | AppHttpControllersAPIProductController || DELETE | /products/{product} | products.destroy| destroy | AppHttpControllersAPIProductController |+--------+-----------+----------------------------+---------------+----------------------------------------------+Controladores API
Section titled “Controladores API”Puedes generar un controlador específico para API con el comando Artisan:
php artisan make:controller API/ProductController --apiEsto creará un controlador con los métodos necesarios para un recurso API:
<?php
namespace AppHttpControllersAPI;
use AppHttpControllersController;use AppModelsProduct;use IlluminateHttpRequest;
class ProductController extends Controller{ /** * Display a listing of the resource. */ public function index() { $products = Product::all(); return response()->json($products); }
/** * Store a newly created resource in storage. */ public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'price' => 'required|numeric', 'description' => 'nullable|string', ]);
$product = Product::create($validated);
return response()->json($product, 201); }
/** * Display the specified resource. */ public function show(Product $product) { return response()->json($product); }
/** * Update the specified resource in storage. */ public function update(Request $request, Product $product) { $validated = $request->validate([ 'name' => 'sometimes|string|max:255', 'price' => 'sometimes|numeric', 'description' => 'nullable|string', ]);
$product->update($validated);
return response()->json($product); }
/** * Remove the specified resource from storage. */ public function destroy(Product $product) { $product->delete();
return response()->json(null, 204); }}Personalización de rutas API
Section titled “Personalización de rutas API”// Limitar acciones disponiblesRoute::apiResource('products', ProductController::class)->only([ 'index', 'show']);
// Excluir acciones específicasRoute::apiResource('products', ProductController::class)->except([ 'destroy']);
// Registrar rutas adicionales para un recursoRoute::apiResource('products', ProductController::class);Route::post('products/{product}/restore', [ProductController::class, 'restore'])->name('products.restore');Respuestas JSON
Section titled “Respuestas JSON”Laravel proporciona varias formas de devolver respuestas JSON desde tus controladores API. Estas respuestas incluyen los encabezados HTTP adecuados (Content-Type: application/json) y convierten automáticamente los datos PHP en JSON.
Métodos básicos para respuestas JSON
Section titled “Métodos básicos para respuestas JSON”// Convertir un array o modelo a JSONreturn response()->json(['name' => 'John', 'age' => 30]);
// Especificar un código de estado HTTPreturn response()->json(['error' => 'No autorizado'], 401);
// Incluir encabezados personalizadosreturn response()->json($data, 200, [ 'X-API-Version' => '1.0', 'X-Custom-Header' => 'Value']);
// Permitir JSONPreturn response()->json($data)->withCallback($request->input('callback'));Transformación de modelos Eloquent
Section titled “Transformación de modelos Eloquent”Los modelos Eloquent y sus colecciones se convierten automáticamente a JSON cuando se devuelven desde rutas o controladores:
// Devolver un modelo como JSONpublic function show(Product $product){ return $product; // Automáticamente convertido a JSON}
// Devolver una colección como JSONpublic function index(){ return Product::all(); // Automáticamente convertido a JSON}
// Devolver una colección paginadapublic function index(){ return Product::paginate(15); // Incluye metadatos de paginación}Personalización de la serialización JSON
Section titled “Personalización de la serialización JSON”Puedes personalizar cómo se serializan tus modelos a JSON utilizando propiedades y métodos en tus modelos Eloquent:
<?php
namespace AppModels;
use IlluminateDatabaseEloquentModel;
class Product extends Model{ /** * Los atributos que deben estar ocultos en la serialización. * * @var array */ protected $hidden = ['created_at', 'updated_at', 'deleted_at'];
/** * Los atributos que deben ser visibles en la serialización. * * @var array */ protected $visible = ['id', 'name', 'price', 'description', 'category_id'];
/** * Los atributos que deben ser convertidos a tipos nativos. * * @var array */ protected $casts = [ 'price' => 'float', 'active' => 'boolean', 'options' => 'array', ];
/** * Los atributos que deben ser añadidos a la serialización. * * @var array */ protected $appends = ['formatted_price', 'is_available'];
/** * Obtener el precio formateado. * * @return string */ public function getFormattedPriceAttribute() { return '$' . number_format($this->price, 2); }
/** * Determinar si el producto está disponible. * * @return bool */ public function getIsAvailableAttribute() { return $this->stock > 0 && $this->active; }}Resource Classes
Section titled “Resource Classes”Las clases Resource proporcionan una capa de transformación entre tus modelos Eloquent y las respuestas JSON. Son especialmente útiles para APIs complejas donde necesitas un control preciso sobre la estructura de tus respuestas.
Crear una Resource Class
Section titled “Crear una Resource Class”php artisan make:resource ProductResource<?php
namespace AppHttpResources;
use IlluminateHttpRequest;use IlluminateHttpResourcesJsonJsonResource;
class ProductResource extends JsonResource{ /** * Transform the resource into an array. * * @return array<string, mixed> */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'price' => $this->price, 'formatted_price' => '$' . number_format($this->price, 2), 'description' => $this->description, 'category' => [ 'id' => $this->category->id, 'name' => $this->category->name, ], 'created_at' => $this->created_at->format('Y-m-d H:i:s'), 'url' => route('api.products.show', $this->id), ]; }
/** * Get additional data that should be returned with the resource array. * * @return array<string, mixed> */ public function with(Request $request): array { return [ 'meta' => [ 'version' => '1.0', 'api_status' => 'stable', ], ]; }}Usar Resource Classes en controladores
Section titled “Usar Resource Classes en controladores”<?php
namespace AppHttpControllersAPI;
use AppHttpControllersController;use AppHttpResourcesProductResource;use AppHttpResourcesProductCollection;use AppModelsProduct;
class ProductController extends Controller{ /** * Display a listing of the resource. */ public function index() { // Usar una colección de recursos return ProductResource::collection(Product::paginate(15)); }
/** * Display the specified resource. */ public function show(Product $product) { // Usar un recurso individual return new ProductResource($product); }}Resource Collections
Section titled “Resource Collections”Para personalizar la estructura de las colecciones, puedes crear una clase ResourceCollection:
php artisan make:resource ProductCollection --collection<?php
namespace AppHttpResources;
use IlluminateHttpRequest;use IlluminateHttpResourcesJsonResourceCollection;
class ProductCollection extends ResourceCollection{ /** * Transform the resource collection into an array. * * @return array<int|string, mixed> */ public function toArray(Request $request): array { return [ 'data' => $this->collection, 'links' => [ 'self' => route('api.products.index'), ], ]; }
/** * Get additional data that should be returned with the resource array. * * @return array<string, mixed> */ public function with(Request $request): array { return [ 'meta' => [ 'products_count' => $this->collection->count(), 'version' => '1.0', ], ]; }}Respuestas de error
Section titled “Respuestas de error”Para APIs, es importante devolver respuestas de error consistentes y bien estructuradas:
// Error 404 - No encontradoreturn response()->json([ 'message' => 'Producto no encontrado', 'error' => 'not_found'], 404);
// Error 422 - Validación fallidareturn response()->json([ 'message' => 'Los datos proporcionados no son válidos', 'errors' => [ 'name' => ['El nombre es obligatorio'], 'price' => ['El precio debe ser un número positivo'] ]], 422);
// Error 403 - Prohibidoreturn response()->json([ 'message' => 'No tienes permiso para realizar esta acción', 'error' => 'forbidden'], 403);
// Error 500 - Error del servidorreturn response()->json([ 'message' => 'Ha ocurrido un error inesperado', 'error' => 'server_error'], 500);Middleware API
Section titled “Middleware API”Los middleware en Laravel permiten filtrar las solicitudes HTTP que ingresan a tu aplicación. El grupo de middleware api se aplica automáticamente a todas las rutas definidas en routes/api.php.
Middleware API predeterminado
Section titled “Middleware API predeterminado”El grupo de middleware api está definido en app/Http/Kernel.php y generalmente incluye:
protected $middlewareGroups = [ 'web' => [ // Middleware web... ],
'api' => [ // LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class, IlluminateRoutingMiddlewareThrottleRequests::class.':api', IlluminateHttpMiddlewareHandleCors::class, IlluminateRoutingMiddlewareSubstituteBindings::class, ],];Este grupo incluye middleware esencial para APIs:
- ThrottleRequests: Limita la tasa de solicitudes para prevenir abusos
- HandleCors: Maneja las solicitudes de origen cruzado (CORS)
- SubstituteBindings: Resuelve las dependencias de inyección de ruta
Crear middleware personalizado para API
Section titled “Crear middleware personalizado para API”Puedes crear middleware personalizado para tus APIs:
php artisan make:middleware EnsureApiVersion<?php
namespace AppHttpMiddleware;
use Closure;use IlluminateHttpRequest;
class EnsureApiVersion{ /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @param string $version * @return mixed */ public function handle(Request $request, Closure $next, $version = '1.0') { // Verificar la versión de la API en el encabezado if ($request->header('X-API-Version') !== $version) { return response()->json([ 'message' => 'Esta versión de la API no es compatible', 'required_version' => $version ], 400); }
return $next($request); }}Registrar middleware personalizado
Section titled “Registrar middleware personalizado”Registra tu middleware en app/Http/Kernel.php:
protected $middlewareAliases = [ // Otros middleware... 'api.version' => AppHttpMiddlewareEnsureApiVersion::class, 'api.key' => AppHttpMiddlewareVerifyApiKey::class,];Aplicar middleware a rutas API
Section titled “Aplicar middleware a rutas API”// Aplicar a una ruta individualRoute::get('/products', [ProductController::class, 'index']) ->middleware('api.version:2.0');
// Aplicar a un grupo de rutasRoute::middleware(['auth:sanctum', 'api.version:2.0']) ->prefix('v2') ->group(function () { Route::apiResource('products', ProductControllerV2::class); });
// Aplicar a todos los endpoints de un controladorRoute::apiResource('products', ProductController::class) ->middleware('api.key');Middleware comunes para APIs
Section titled “Middleware comunes para APIs”Throttling (limitación de tasa)
Section titled “Throttling (limitación de tasa)”Limita el número de solicitudes que un cliente puede hacer en un período de tiempo:
// Limitar a 60 solicitudes por minutoRoute::middleware('throttle:60,1') ->group(function () { Route::apiResource('products', ProductController::class); });
// Diferentes límites para rutas específicasRoute::get('/status', [ApiController::class, 'status']) ->middleware('throttle:120,1'); // 120 solicitudes por minuto
Route::post('/webhooks', [WebhookController::class, 'handle']) ->middleware('throttle:1000,1'); // 1000 solicitudes por minutoCORS (Cross-Origin Resource Sharing)
Section titled “CORS (Cross-Origin Resource Sharing)”Configura CORS en config/cors.php:
return [ 'paths' => ['api/*'], 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => false,];Para una configuración más restrictiva y segura:
return [ 'paths' => ['api/*'], 'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], 'allowed_origins' => ['https://myapp.com', 'https://admin.myapp.com'], 'allowed_origins_patterns' => ['/^https://.*.myapp.com$/'], 'allowed_headers' => ['X-API-KEY', 'Origin', 'Content-Type', 'X-Auth-Token', 'Authorization'], 'exposed_headers' => ['X-API-Version', 'X-Request-Id'], 'max_age' => 60 * 60 * 24, // 24 horas 'supports_credentials' => true,];Registro y monitoreo de API
Section titled “Registro y monitoreo de API”Middleware para registrar todas las solicitudes API:
<?php
namespace AppHttpMiddleware;
use Closure;use IlluminateHttpRequest;use IlluminateSupportFacadesLog;
class LogApiRequests{ /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle(Request $request, Closure $next) { // Registrar la solicitud entrante $startTime = microtime(true);
// Procesar la solicitud $response = $next($request);
// Calcular el tiempo de respuesta $duration = microtime(true) - $startTime;
// Registrar detalles de la solicitud y respuesta Log::channel('api')->info('API Request', [ 'method' => $request->method(), 'url' => $request->fullUrl(), 'user' => $request->user() ? $request->user()->id : 'guest', 'ip' => $request->ip(), 'duration' => round($duration * 1000, 2) . 'ms', 'status' => $response->getStatusCode(), 'content_length' => $response->headers->get('Content-Length') ?? 'unknown', ]);
return $response; }}Autenticación con Sanctum o Passport
Section titled “Autenticación con Sanctum o Passport”Laravel ofrece dos paquetes oficiales para la autenticación de APIs: Sanctum y Passport. Cada uno tiene sus propios casos de uso y ventajas.
Laravel Sanctum es una solución ligera para la autenticación de APIs y SPAs (Single Page Applications). Es ideal para aplicaciones que necesitan tokens API simples o autenticación basada en cookies para SPAs.
Instalación de Sanctum
Section titled “Instalación de Sanctum”composer require laravel/sanctum
php artisan vendor:publish --provider="LaravelSanctumSanctumServiceProvider"
php artisan migrateConfiguración
Section titled “Configuración”Agrega el middleware de Sanctum en app/Http/Kernel.php:
'api' => [ LaravelSanctumHttpMiddlewareEnsureFrontendRequestsAreStateful::class, IlluminateRoutingMiddlewareThrottleRequests::class.':api', IlluminateHttpMiddlewareHandleCors::class, IlluminateRoutingMiddlewareSubstituteBindings::class,],Asegúrate de que tu modelo User use el trait HasApiTokens:
<?php
namespace AppModels;
use IlluminateFoundationAuthUser as Authenticatable;use LaravelSanctumHasApiTokens;
class User extends Authenticatable{ use HasApiTokens;
// ...}Laravel Passport es una implementación completa de OAuth2 para Laravel. Es ideal para APIs más complejas que requieren diferentes tipos de tokens, autorización de terceros, etc.
Instalación de Passport
Section titled “Instalación de Passport”composer require laravel/passport
php artisan migrate
php artisan passport:installConfiguración
Section titled “Configuración”Agrega el trait HasApiTokens a tu modelo User:
<?php
namespace AppModels;
use IlluminateFoundationAuthUser as Authenticatable;use LaravelPassportHasApiTokens;
class User extends Authenticatable{ use HasApiTokens;
// ...}Registra las rutas de Passport en app/Providers/AuthServiceProvider.php:
<?php
namespace AppProviders;
use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider;use LaravelPassportPassport;
class AuthServiceProvider extends ServiceProvider{ protected $policies = [ // ... ];
public function boot(): void { $this->registerPolicies();
Passport::routes();
// Opcional: configurar tiempo de expiración de tokens Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); Passport::personalAccessTokensExpireIn(now()->addMonths(6)); }}Configura el driver de autenticación en config/auth.php:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ],
'api' => [ 'driver' => 'passport', 'provider' => 'users', ],],¿Cuál elegir?
Section titled “¿Cuál elegir?”- Necesitas una solución simple y ligera
- Estás construyendo una SPA que se comunica con una API de Laravel
- Necesitas tokens API para servicios de primera parte
- Quieres autenticación basada en tokens sin la complejidad de OAuth
- Necesitas autenticación para WebSockets con Laravel Echo
- Necesitas una implementación completa de OAuth2
- Tu API será consumida por aplicaciones de terceros
- Necesitas flujos de autorización como Authorization Code, Client Credentials, etc.
- Requieres una interfaz de administración de clientes OAuth
- Tienes requisitos complejos de tokens y permisos
Autenticación con Sanctum
Section titled “Autenticación con Sanctum”Crear tokens API
Section titled “Crear tokens API”<?php
namespace AppHttpControllersAPI;
use AppModelsUser;use IlluminateHttpRequest;use IlluminateSupportFacadesHash;use IlluminateValidationValidationException;
class AuthController extends Controller{ public function login(Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', 'device_name' => 'required', ]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) { throw ValidationException::withMessages([ 'email' => ['Las credenciales proporcionadas son incorrectas.'], ]); }
// Crear token con habilidades/permisos $token = $user->createToken($request->device_name, ['products:read'])->plainTextToken;
return response()->json([ 'token' => $token, 'user' => $user, ]); }
public function logout(Request $request) { // Revocar el token actual $request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Sesión cerrada correctamente']); }
public function user(Request $request) { return response()->json($request->user()); }}Proteger rutas con Sanctum
Section titled “Proteger rutas con Sanctum”use AppHttpControllersAPIAuthController;use AppHttpControllersAPIProductController;
// Rutas públicasRoute::post('/login', [AuthController::class, 'login']);
// Rutas protegidasRoute::middleware('auth:sanctum')->group(function () { Route::get('/user', [AuthController::class, 'user']); Route::post('/logout', [AuthController::class, 'logout']);
// Verificar habilidades/permisos específicos Route::get('/products', [ProductController::class, 'index']) ->middleware('ability:products:read');
Route::post('/products', [ProductController::class, 'store']) ->middleware('ability:products:create');});Autenticación con Passport
Section titled “Autenticación con Passport”Configurar clientes OAuth
Section titled “Configurar clientes OAuth”Puedes crear clientes OAuth mediante comandos Artisan:
// Cliente para Authorization Code Grantphp artisan passport:client
// Cliente para Client Credentials Grantphp artisan passport:client --client
// Cliente para Password Grantphp artisan passport:client --passwordEjemplo de autenticación con Password Grant
Section titled “Ejemplo de autenticación con Password Grant”<?php
namespace AppHttpControllersAPI;
use IlluminateHttpRequest;use IlluminateSupportFacadesHttp;
class AuthController extends Controller{ public function login(Request $request) { $request->validate([ 'email' => 'required|email', 'password' => 'required', ]);
$response = Http::post(config('app.url') . '/oauth/token', [ 'grant_type' => 'password', 'client_id' => config('passport.password_grant_client.id'), 'client_secret' => config('passport.password_grant_client.secret'), 'username' => $request->email, 'password' => $request->password, 'scope' => '', ]);
return $response->json(); }
public function logout(Request $request) { $request->user()->token()->revoke();
return response()->json(['message' => 'Sesión cerrada correctamente']); }
public function user(Request $request) { return response()->json($request->user()); }}Proteger rutas con Passport
Section titled “Proteger rutas con Passport”use AppHttpControllersAPIAuthController;use AppHttpControllersAPIProductController;
// Rutas públicasRoute::post('/login', [AuthController::class, 'login']);
// Rutas protegidasRoute::middleware('auth:api')->group(function () { Route::get('/user', [AuthController::class, 'user']); Route::post('/logout', [AuthController::class, 'logout']);
// Verificar scopes específicos Route::get('/products', [ProductController::class, 'index']) ->middleware('scope:read-products');
Route::post('/products', [ProductController::class, 'store']) ->middleware('scope:create-products');});Buenas prácticas de seguridad para APIs
Section titled “Buenas prácticas de seguridad para APIs”Conclusión
Section titled “Conclusión”Laravel proporciona un conjunto completo de herramientas para crear APIs RESTful robustas y seguras. Desde la definición de rutas y controladores hasta la serialización de respuestas JSON y la autenticación mediante tokens, el framework facilita el desarrollo de APIs modernas que siguen las mejores prácticas.
Al combinar estas características con otras funcionalidades de Laravel como validación, middleware, y manejo de excepciones, puedes construir APIs escalables y mantenibles que satisfagan las necesidades de tus clientes y aplicaciones frontend.