5. API REST
5.1. Diseño de endpoints
La arquitectura de la API sigue los principios REST (Representational State Transfer), proporcionando una interfaz coherente y predecible para interactuar con los recursos del sistema.
Convenciones de diseño
La API está diseñada siguiendo estas convenciones:
-
Recursos como sustantivos en plural: Los endpoints representan recursos y se nombran como sustantivos en plural (ej:
/products,/carts). -
Acciones implícitas por método HTTP: En lugar de endpoints específicos para acciones, se utilizan métodos HTTP para indicar la operación:
GET: Lectura de recursosPOST: Creación de recursosPUT: Actualización completa de recursosDELETE: Eliminación de recursos
-
Jerarquía modular: Los endpoints se organizan siguiendo la estructura modular del sistema:
/[módulo]/[recurso]/[identificador]/[subrecurso]Por ejemplo:
/products(recurso base)/permut/products(recurso en módulo específico)/tpv/carts/line(subrecurso)
-
Formato de respuesta uniforme: Todas las respuestas siguen un formato JSON consistente.
Estructura de un recurso REST
Cada recurso en la API típicamente expone los siguientes endpoints:
| Método HTTP | Endpoint | Acción | Descripción |
|---|---|---|---|
| GET | /{recurso} | index | Lista colección de recursos (paginados) |
| GET | /{recurso}/:id | show | Obtiene un recurso específico |
| POST | /{recurso} | store | Crea un nuevo recurso |
| PUT | /{recurso}/:id | update | Actualiza un recurso existente |
| DELETE | /{recurso}/:id | delete | Elimina un recurso |
Para subrecursos o acciones especializadas, se siguen patrones adicionales:
| Método HTTP | Endpoint | Acción | Descripción |
|---|---|---|---|
| POST | /{recurso}/line | storeLine | Crea un subrecurso |
| PUT | /{recurso}/line/:id | updateLine | Actualiza un subrecurso |
| DELETE | /{recurso}/line/:id | deleteLine | Elimina un subrecurso |
5.2. Estructura de rutas
Las rutas se definen de manera declarativa en archivos dedicados, utilizando una API fluida proporcionada por la clase Route.
Definición de rutas
Cada dominio tiene su propio archivo de rutas (por ejemplo, ProductsRoutes.php) donde se definen sus endpoints:
// src/ApiLayer/Domains/Products/ProductsRoutes.php
namespace ApiLayer\Domains\Products;
use App\Core\Router\Route;
use ApiLayer\Domains\Products\ProductsController;
use App\Middlewares\AuthMiddleware;
$middlewares = ['before' => [AuthMiddleware::class]];
Route::group(ProductsController::class, function () use ($middlewares) {
Route::get('products/', 'index', $middlewares);
Route::get('products/:id', 'show', $middlewares);
Route::post('products/', 'store', $middlewares);
Route::put('products/:id', 'update', $middlewares);
Route::delete('products/:id', 'delete', $middlewares);
});
Agrupación de rutas
Las rutas se agrupan por controlador utilizando Route::group(), lo que facilita la organización y la aplicación de middlewares comunes:
Route::group(CartsController::class, function () use ($middlewares) {
// Definición de rutas para el controlador de carritos
});
Rutas con parámetros
Los parámetros dinámicos se definen con el prefijo : en la definición de la ruta:
Route::get('products/:id', 'show', $middlewares);
Estos parámetros son extraídos y pasados al método del controlador.
Rutas modulares
Los módulos extienden la estructura de rutas con prefijos específicos:
// src/ApiLayer/Modules/Permut/Domains/Products/ProductsRoutes.php
Route::group(ProductsController::class, function () use ($middlewares) {
Route::get('permut/products/', 'index', $middlewares);
Route::get('permut/products/:id', 'show', $middlewares);
// ...
});
Resolución de rutas
El proceso de resolución de rutas ocurre en QuRouter::resolve() y sigue estos pasos:
- Analiza la URL de la solicitud para extraer el path
- Busca el archivo de rutas correspondiente
- Busca una coincidencia entre las rutas registradas y el path solicitado
- Extrae parámetros dinámicos de la URL
- Determina el controlador y método a ejecutar
- Identifica middlewares asociados
- Retorna la información de la ruta resuelta al kernel
5.3. Convenciones de recursos
La API implementa convenciones consistentes para recursos que facilitan el uso y mantenimiento.
Recursos de dominio
Cada recurso de dominio expone operaciones CRUD estándar:
// Ejemplo de rutas de un recurso
Route::get('products/', 'index'); // Listar todos (paginado)
Route::get('products/:id', 'show'); // Obtener uno
Route::post('products/', 'store'); // Crear
Route::put('products/:id', 'update'); // Actualizar
Route::delete('products/:id', 'delete'); // Eliminar
Colecciones paginadas
Los endpoints que devuelven colecciones implementan paginación, ordenamiento y filtrado:
GET /products?page=2&itemsPerPage=20&sortBy=["name"]&sortDesc=[true]&filters={"name":{"like":"keyword"}}
La respuesta incluye metadatos de paginación:
{
"collection": [...],
"totalRows": 150,
"totalFilteredRows": 42,
"pages": 3,
"page": 2,
"itemsPerPage": 20
}
Subrecursos
Para modelar relaciones, la API utiliza subrecursos anidados:
// Ejemplo de rutas para líneas de carrito
Route::post('tpv/carts/line', 'storeLine');
Route::put('tpv/carts/line/:id', 'updateLine');
Route::delete('tpv/carts/line/:id', 'deleteLine');
Formatos de entrada y salida
La API utiliza DTOs (Data Transfer Objects) para estructurar los datos de entrada y salida:
ReqDTO: Define la estructura esperada de los datos de entradaResDTO: Define el formato de los datos de salida
Estos objetos gestionan la transformación y validación de datos.
5.4. Gestión de respuestas
La API proporciona respuestas HTTP consistentes utilizando la clase ResponseFactory.
Factory de respuestas
El método helper response() proporciona una instancia de ResponseFactory para generar respuestas HTTP:
// Ejemplo de respuesta JSON
return response()->json($resource, 200);
Formato de respuestas JSON
Las respuestas JSON siguen una estructura consistente:
- Para recursos individuales: El objeto directamente
- Para colecciones: Un objeto con metadatos de paginación
// Ejemplo de respuesta para un recurso
public function show($id)
{
$resource = $this->service->selectById($id);
if ($resource === null) {
return response()->json(['error' => 'Resource not found'], 404);
}
return response()->json($resource, 200);
}
Códigos de estado HTTP
La API utiliza códigos de estado HTTP estándar:
| Código | Significado | Uso |
|---|---|---|
| 200 | OK | Éxito en operaciones GET, PUT |
| 201 | Created | Éxito en operaciones POST |
| 400 | Bad Request | Error de validación |
| 401 | Unauthorized | Error de autenticación |
| 403 | Forbidden | Sin permisos suficientes |
| 404 | Not Found | Recurso no encontrado |
| 405 | Method Not Allowed | Método HTTP no permitido |
| 422 | Unprocessable Entity | Datos válidos pero inaceptables |
| 429 | Too Many Requests | Rate limiting excedido |
| 500 | Internal Server Error | Error interno del servidor |
Respuestas de error
Las respuestas de error siguen un formato consistente:
{
"error": "Mensaje descriptivo del error",
"code": 400,
"errors": {
"campo1": ["El campo es requerido"],
"campo2": ["El formato es inválido"]
}
}
En modo de desarrollo (ENVIRONMENT == "DEV"), las respuestas de error incluyen información adicional:
{
"error": "Mensaje descriptivo del error",
"code": 400,
"file": "/path/to/file.php",
"line": 123,
"errors": { ... }
}
Transformación de datos
La respuesta final pasa por varias capas de transformación:
- Servicios: Recuperan datos de repositorios y los transforman mediante DTOs
- DTOs: Estructuran y formatean los datos para la presentación
- Controladores: Empaquetan los datos en respuestas HTTP con códigos de estado
- Middlewares: Pueden modificar o enriquecer la respuesta
- ResponseFactory: Gestiona cabeceras, codificación y envío
Este enfoque garantiza respuestas coherentes y bien estructuradas en toda la API.
Manejo de CORS
La API implementa soporte para Cross-Origin Resource Sharing (CORS):
// Configuración de cabeceras CORS en ResponseFactory.php
protected function getDefaultHeaders()
{
// Determinar origen de la solicitud
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
// Cargar cabeceras predeterminadas y orígenes permitidos
$defaultHeaders = Config::defaultHeaders();
$allowedOrigins = Config::allowedOrigins();
// Configurar cabecera Access-Control-Allow-Origin
if (is_array($allowedOrigins)) {
if (in_array($origin, $allowedOrigins)) {
$defaultHeaders['Access-Control-Allow-Origin'] = $origin;
}
}
if (is_string($allowedOrigins)) {
$defaultHeaders['Access-Control-Allow-Origin'] = $allowedOrigins;
}
return $defaultHeaders;
}
Las solicitudes OPTIONS se manejan automáticamente para soportar preflight CORS:
// Manejo automático de solicitudes OPTIONS en index.php
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
$res = new ResponseFactory;
$res->corsOptions();
$res->send();
exit(0);
}
Este sistema proporciona una API REST completa y bien estructurada que sigue principios de diseño modernos y facilita la interacción con los recursos del sistema.