Newsletter para devsEntra

Como una librería me obligó a programar toda una API. Parte III: Gestión de rutas.

En la continuación de esta serie en la que construimos una API, hemos pasado por tener que tomar la decisión sobre qué stack utilizar, elegir si debíamos utilizar programación a medida, un CMS o un framework, las ventajas de utilizar las variable de entorno para almacenar información sensible y por último la utilización de espacio de nombres para facilitar la tarea de tener que hacer referencia a dependencias propias y de librerías.

Si aún no os habéis topado con esta serie, os dejo los enlaces a las mismas:

Hoy nos enfrentamos a la tercera parte de esta serieun pilar fundamental dentro de la programación de una API: la gestión de rutas.

Gestión de rutas en una API REST

Está de sobra señalar que las rutas son la puerta de entrada en una API. Ese sitio mágico donde se analizan las peticiones, se trabaja con ellas y se muestra una respuesta al usuario o la aplicación que realizan las mismas.

Os pongo un par de ejemplos de APIs que nos podemos encontrar por internet:

  • The Star Wars API: API que te permite obtener información sobre Star Wars.
  • Weather API: API que te permite obtener la información meteorológica basada en varios parámetros.

Si alguna vez habéis utilizado alguna de estas API, os habréis dado cuenta que todas funcionan de una forma similar. Atacamos a una URL que nos devuelve una información en formato JSON (normalmente).

Por ejemplo, en el caso de la API de Star Wars, si visitamos la URL base: https://swapi.dev/api/ pero además le añadimos planets/3/nos devuelve un JSON con información del planeta con id=3.

En este caso, el JSON de salida se ve así:

{
    'name': 'Yavin IV',
    'rotation_period': '24',
    'orbital_period': '4818',
    'diameter': '10200',
    'climate': 'temperate, tropical',
    'gravity': '1 standard',
    'terrain': 'jungle, rainforests',
    'surface_water': '8',
    'population': '1000',
    'residents': [],
    'films': [
        'http://swapi.dev/api/films/1/'
    ],
    'created': '2014-12-10T11:37:19.144000Z',
    'edited': '2014-12-20T20:58:18.421000Z',
    'url': 'http://swapi.dev/api/planets/3/'
}

Genial, ¿verdad?

Hay tres conceptos importantes que quiero que veamos:

  • URL base: que es la URL del dominio que contiene la definición de rutas de la API. En este caso: https://swapi.dev/api/
  • EndPoints: las direcciones definidas a la espera de una petición HTTP. En este caso: planets.
  • Parámetro: un parámetro introducido en la peitción por el usuario y que el endPoint procesará en busca de información referente a esa entidad y ese parámetro. En este caso el id número 3.

Pues bien, esto es lo que pretendemos llevar a cabo en nuestra API. Una deifnición de los distintos endPoints de nuestra aplicación, que serán las rutas aceptadas por nuestra aplicación para recibir peticiones externas a través del protocolo HTTP (denominadas requests).

No os liéis con los terminos termino: Cuando hablamos de peticiones HTTP, simplemente nos estamos refiriendo al punto en el que un usuario escribe una dirección WEB en el navegador o bien otra aplicación lanza una petición HTTP a nuestra API. En este último caso tenemos CURL para PHP, fetch para Javascript, etc.

Cómo gestionar las rutas en nuestra aplicación API

Cabe destacar que estamos abordando una gestión de peticiones de endPoints para una REST API, ya que otros modelos de API se gestionan de una forma distinta a la que veremos a continuación.

Como bien sabéis, la API que estamos programando utiliza el lenguaje PHP, y como hemos visto en las anteriores entregas, la batería de librerías que hemos seleccionado es la de The PHP League, que contienen la mayoría de librerías que nos ayudarán a llevar a cabo tareas referentes a una API.

La gestión de rutas en ThePHPLeague se lleva a cabo a través de la librería Route, y en este caso, en su versión compatible con código PHP en versión 7.1.0 o superior.

Vayamos a la carpeta del proyecto y como tenemos por costumbre, el primer paso para poder instalar la librería es ejecutar el comando composer que nos ayudará a instalar la misma desde la consola:

composer require league/route

Tal y como reza la documentación de Route, es necesario incluir require 'vendor/autoload.php' en la entrada de nuestra aplicación (en nuestro caso index.php). Recordad pasaros por la parte II, donde os explicamos porqué es necesario incluir esta línea en nuestro proyecto.

El siguiente paso es incluir una librería que realice la gestión de respuestas de las peticiones hechas por el protocolo HTTP. En este caso, esta gestión la lleva a cabo el protocolo PSR-7 (recordad que ya instalamos el protocolo PSR-4 para la gestión de espacio de nombres en la parte II de esta saga).

Para la instalación de PSR-7 utilizaremos laminas-diactoros:

composer require laminas/laminas-diactoros

Si hemos decidido utilizar laminas-diactoros, también necesitaremos ejecutar el siguiente comando:

composer require laminas/laminas-httphandlerrunner

Una vez instaladas estas librerías ya tendremos todo listo para ponernos a trabajar con nuestras rutas.

En este caso vamos a implementarlas directamente en nuestro archivo index.php.

Lo primero es incluir las dependencias a librerías para la gestión de rutas a través del comando use:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

A continuación especificaremos los tipos de petición que nuestra API será capaz de soportar (GET, POST, etc.).

$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(
    $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
);

Declaramos el gestor de respuestas, la estrategía a seguir (en nuestro caso será JSON, ya que estamos programando una API) y la declaración del enrrutador, que se encargará de la gestión de las rutas:

$responseFactory = new \Laminas\Diactoros\ResponseFactory();

$strategy = new League\Route\Strategy\JsonStrategy($responseFactory);
$router   = (new League\Route\Router)->setStrategy($strategy);

Ahora llega el momento de declarar las rutas y la funcionalidad que las mismas tendrán.

Para no extendernos en el artículo, declararemos una ruta simple (al endPoint apiVersion), que aceptará peticiones GET y que mostrará devolverá un JSON simple con un texto fijo y el nombre de la API :

$router->map('GET', '/apiVersion', function (ServerRequestInterface $request) : array {
    return [
        'textoRetorno'   => 'Esta es una respuesta desde la API',
        'version' => 1,
    ];
});

Como podemos ver el código es muy sencillo, ejecutaremos el método del enrrutador llamado map, que acepta los siguientes parámetros:

  • El método de petición: en este caso GET.
  • El endPoint: /apiVersion.
  • Una función anónima que tiene como parámetro la petición realizada a la API y que retorna un array con los índices textoRetorno y version.

Cabe destacar que en este caso no hemos tenido que convertir el array a JSON debido a que anteriormente hemos declarado el enrrutador con la estrategia JSON. Esto hace que la misma librería transforme la respuesta en tipo JSON.

Por último sólo necesitaremos emitir la respuesta y enviarla al navegador con estos dos comandos:

$response = $router->dispatch($request);

(new Laminas\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

Nuestro código en index.php quedará finalmente así:

<?php

require 'vendor/autoload.php';

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(
    $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
);

$responseFactory = new \Laminas\Diactoros\ResponseFactory();

$strategy = new League\Route\Strategy\JsonStrategy($responseFactory);
$router   = (new League\Route\Router)->setStrategy($strategy);

$router->map('GET', '/apiVersion', function (ServerRequestInterface $request) : array {
    return [
        'textoRetorno'   => 'Esta es una respuesta desde la API',
        'version' => 1,
    ];
});

$response = $router->dispatch($request);

// send the response to the browser
(new Laminas\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

Por último, sólo tenemos pendiente configurar nuestra ruta para que acepte parámetros.

Para este cometido, necesitaremos modificar nuestra ruta para que acepte los mismos, y esto se lleva a cabo añadiendo {/parametro} a la ruta anteriormente declarada, añadir el parámetro a la función anómina de la función y mostrando el valor del mismo dentro de nuestro objeto JSON.

Vamos a ver cómo quedaría en nuestro código pasando a la URL el parámetro con valor 3:

<?php 

require 'vendor/autoload.php';

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

$request = Laminas\Diactoros\ServerRequestFactory::fromGlobals(
    $_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
);

$responseFactory = new \Laminas\Diactoros\ResponseFactory();

$strategy = new League\Route\Strategy\JsonStrategy($responseFactory);
$router   = (new League\Route\Router)->setStrategy($strategy);

$router->map('GET', '/apiVersion/{parametro}', function (ServerRequestInterface $request, array $args) : array {
    return [
        'textoRetorno'   => 'Esta es una respuesta desde la API',
        'version' => 1,
        'parametro' => $args['parametro']
    ];
});

$response = $router->dispatch($request);

// send the response to the browser
(new Laminas\HttpHandlerRunner\Emitter\SapiEmitter)->emit($response);

Una vez modificado nuestro código, la respuesta en el navegador quedaría así:

{
'textoRetorno': 'Esta es una respuesta desde la API',
'version': 1,
'parametro': '3'
}

Un último apunte, y es que si decidís implementar esta gestión de rutas en un servidor Apache, tendréis que incluir un archivo .htaccess en el raíz de vuestro proyecto para que la gestión de rutas que penden de / se gestione correctamente.

Si este es vuestro caso, cread un archivo .htaccessen el raíz de vuestro proyecto que contenga las reglas correspondientes de RewriteRule:

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.php [QSA,L]
</IfModule>

Conclusión

Tal y como hemos visto, la generación de los distintos endPoints a través de la creación de rutas en nuestra aplicación se torna una tarea bastante sencilla con la instalación de la librería Route de ThePHPLeague.

En este punto tan sólo tendremos que decidir qué rutas queremos que nuestra API acepte, el tipo de petición de las mismas y el formato de la respuesta. En esta aplicación hemos optado por utilizar peticiones el tipo GET, pero tal y como hemos especificado anteriormente podríamos utilizar POST, PUT, etc.

La generación de una API no siempre tiene que realizarse a través de librerías. Si decidís optar por la opción en la que vosotros mismo generéis las peticiones y respuestas, os aconsejo un curso en el que Daniel Primo genera una API desde cero y además con un plus de refactorización muy interesante. Podéis encontrar encontrar este curso en este link:

Crear una API REST artesana con PHP

Recordad que podéis encontrar todo el código de esta aplicación en el reponsitorio oficial: php-routes-basic.

!Nos vemos en el siguiente capítulo!

Escrito originalmente por: Juan José Ramos

Imagen de Daniel Primo

Daniel Primo

CEO en pantuflas de Web Reactiva. Programador y formador en tecnologías que cambian el mundo y a las personas. @delineas en twitter y canal @webreactiva en telegram

12 recursos para developers cada domingo en tu bandeja de entrada

Además de una skill práctica bien explicada, trucos para mejorar tu futuro profesional y una pizquita de humor útil para el resto de la semana. Gratis.