Skip to main content

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:

  1. Recursos como sustantivos en plural: Los endpoints representan recursos y se nombran como sustantivos en plural (ej: /products, /carts).

  2. 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 recursos
    • POST: Creación de recursos
    • PUT: Actualización completa de recursos
    • DELETE: Eliminación de recursos
  3. 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)
  4. 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 HTTPEndpointAcciónDescripción
GET/{recurso}indexLista colección de recursos (paginados)
GET/{recurso}/:idshowObtiene un recurso específico
POST/{recurso}storeCrea un nuevo recurso
PUT/{recurso}/:idupdateActualiza un recurso existente
DELETE/{recurso}/:iddeleteElimina un recurso

Para subrecursos o acciones especializadas, se siguen patrones adicionales:

Método HTTPEndpointAcciónDescripción
POST/{recurso}/linestoreLineCrea un subrecurso
PUT/{recurso}/line/:idupdateLineActualiza un subrecurso
DELETE/{recurso}/line/:iddeleteLineElimina 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:

  1. Analiza la URL de la solicitud para extraer el path
  2. Busca el archivo de rutas correspondiente
  3. Busca una coincidencia entre las rutas registradas y el path solicitado
  4. Extrae parámetros dinámicos de la URL
  5. Determina el controlador y método a ejecutar
  6. Identifica middlewares asociados
  7. 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 entrada
  • ResDTO: 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ódigoSignificadoUso
200OKÉxito en operaciones GET, PUT
201CreatedÉxito en operaciones POST
400Bad RequestError de validación
401UnauthorizedError de autenticación
403ForbiddenSin permisos suficientes
404Not FoundRecurso no encontrado
405Method Not AllowedMétodo HTTP no permitido
422Unprocessable EntityDatos válidos pero inaceptables
429Too Many RequestsRate limiting excedido
500Internal Server ErrorError 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:

  1. Servicios: Recuperan datos de repositorios y los transforman mediante DTOs
  2. DTOs: Estructuran y formatean los datos para la presentación
  3. Controladores: Empaquetan los datos en respuestas HTTP con códigos de estado
  4. Middlewares: Pueden modificar o enriquecer la respuesta
  5. 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.