Skip to content

6 - API

Introducción

Enlace Clase 6 API

Apartados:

0:00:00 - Introducción 0:01:25 - Rutas Api 0:20:44 - Controlador Api 0:35:08 - Tipado En Controlador Y Prueba De Funcionamiento 0:50:00 - Api Resource 1:00:57 - Conclusiones

Al trabajar con Laravel, no sólo trabajaremos con Monolito, sino que también lo haremos como BackEnd puro,

  • Serviremos los datos a través de una API REST (enlace)
  • Es importante comprender la teoría de API Rest, sus verbos y las técnicas y tecnologías más usadas.
  • Un segundo sistema, frontal, hace el consumo desde React, VUE...
  • CRUD a través de API.

1738074553383

1. Creación API y Rutas Api

  1. Crear Proyecto
composer create-project laravel/laravel apicrud
INFO  Application key set successfully. 

Pueden ser entornos complementarios el hecho de tener un monolito y una API Crud en el mismo proyecto, por ejemplo tener un panel de admin con vistas y una API Rest para servir a un cliente.

Laravel 11 install:api

Pequeña actualización para Laravel 11, puesto que no trae el API por defecto.

  1. Ejecutar en la terminal
  2. ︎php artisan install:api
  3. Añadir en el archivo User.php︎
  4. use Laravel\Sanctum\HasApiTokens

1738075138153


No necesariamente tendremos sistemas puramente backend o sistemas front, pueden ser sistemas híbridos

2. Crear BBDD

En el tutorial, va a crear la base de datos en Mysql llamada apicrud, pero nosotros lo vemos en sqlite Ruta: .env

Cambiamos Mysql por sqlite y comentamos el resto del bloque

DB_CONNECTION=sqlite
#DB_HOST=127.0.0.1
#DB_PORT=3306
#DB_DATABASE=laravel
#DB_USERNAME=root
#DB_PASSWORD=

3. Modelo Note y la migración

Al igual que en anterior proyecto CRUD que hicimos mediante vistas, vamos a realizar el modelo nota pero esta vez para servirlo con una api rest

php artisan make:model Note --migration

Con lo que obtenemos el OK y las rutas donde se ubican:

    INFO  Model [app/Models/Note.php] created successfully.  

   INFO  Migration [database/migrations/2023_12_19_063948_create_notes_table.php] created successfully. 

Modelo

Vamos al modelo para preparar elementos

Model [app/Models/Note.php]

Cuales serán accesibles? oculta si algo no quieres devolverlo en el JSON de respuesta, con Hidden... cumplimentables / fillable, o cuales vamos a proteger Como solo va a único elemento con título y descripción, por lo tanto usamos el atajo con un array, diciendo que está vacio:

 class Note extends Model
{
    use HasFactory;
    protected $guarded =[]; // si está vacío quiere decir que todos son cumplimentables, ya que este es el opuesto

}

Migración

  1. Vamos a migrations para preparar la creación de la tabla notes y añadir los campos necesarios.
    ruta:   Migration [database/migrations/2023_12_19_063948_create_notes_table.php] 
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->string('title', 255); //nuevo campo titulo
            $table->string('content', 255)->nullable(); //nuevo campo content
            $table->timestamps();

        });
    }

php artisan migrate

Rutas API, (Enlace)

Ahora ya no van a ir en el archivo web.php sino en el archivo routes/api.php (recuerda haber instalado api desde Laravel 11)

  • Al igual que en las rutas web, tenemos un route dispacher para los diferentes métodos.
  • Donde se asocian cada una de las URI con su controlador
  • Vamos a hacer lo mismo pero en las rutas api.php

Todavía no la vamos a proteger, haremos una api publica, por lo que comentamos la ruta que trae por defecto Laravel con el middleware Auth Sanctum

APIs públicas

Como seguro sabrás, existen múltiples APIs públicas para prácticar y consumir sus datos desde el cliente

1738075672603

Controlador --resource

Si queremos hacer la gestión de un CRUD podemos ahorrarnos la creación de todas las funciones con añadiéndole --resource a la creación del controlador

php artisan make:controller NoteController --resource

Así ya Laravel nos hace todas las funciones HTTP principales, show, update, store..

class NoteController extends Controller
{
    public function index()
    {
    ...

Api.php

Hay que incorporar el controlador en la ruta api.php

use app\Http\Controllers\NoteController;

Si hacemos un CRUD, en vez crear de siete rutas, creamos el método estático resource Si por lo que sea hacemos algo más que no sea CRUD , no será necesario incorporarla aquí ya que en principio no utilizaremos vistas.

span

Para poder conocer todas las rutas creadas por resource:

php artisan route:list

  GET|HEAD        / ......................................................................................................................................................... 
  POST            _ignition/execute-solution .................................................. ignition.executeSolution  Spatie\LaravelIgnition  ExecuteSolutionController
  GET|HEAD        _ignition/health-check .............................................................. ignition.healthCheck  Spatie\LaravelIgnition  HealthCheckController
  POST            _ignition/update-config ........................................................... ignition.updateConfig  Spatie\LaravelIgnition  UpdateConfigController
  GET|HEAD        api/note ................................................................................................................ note.index  NoteController@index
  POST            api/note ................................................................................................................ note.store  NoteController@store
  GET|HEAD        api/note/create ....................................................................................................... note.create  NoteController@create
  GET|HEAD        api/note/{note} ........................................................................................................... note.show  NoteController@show
  PUT|PATCH       api/note/{note} ....................................................................................................... note.update  NoteController@update
  DELETE          api/note/{note} ..................................................................................................... note.destroy  NoteController@destroy
  GET|HEAD        api/note/{note}/edit ...................................................................................................... note.edit  NoteController@edit
  GET|HEAD        sanctum/csrf-cookie ..................................................................... sanctum.csrf-cookie  Laravel\Sanctum  CsrfCookieController@show

Prefijo /api/note

FÍJATE cómo, por defecto, lleva delante la palabra api ya que Laravel le añade ese prefijo, aunque se puede cambiar en Providers/RouteServiceProvider en la función boot Además de esto, podemos añadir más prefijos desde la ruta en api.php:

En este provider va a diferenciar las rutas API y las rutas WEB y va a establecer el límite de las peticiones. el group indica donde están escritas las rutas de ambas partes.

Límite de solicitudes

Un punto importante es la configuración del límite de solicitudes que describe la función configureRateLimiting Numero de peticiones máximas, por defecto indica que el número es de 60, lo cual es poco para la mayoría de sistemas.

Para nosotros podría valer. Pero en un ejemplo de alguna red social, si estamos chequeando favoritos de una lista de 100, con una sola petición hemos desbordado este límite Es una limitación por cada uno de los usuarios

Modificado

En la versión actual no lo carga por defecto, para poder investigar e implementarlo, debesconsultar la documentación oficial

Controlador Api (enlace)

NoteController / Función index

ruta del fichero: API/Http/Controllers/NoteControllers

  1. Lo primero, incluir las librerías del modelo.

Para poder hacer uso del ORM Eloquent, el que trae Laravel por defecto aunque hay otras opciones: Ya veremos cuándo es interesante otros

Primero agregamos la librería del modelo

use  App\Models\Note;

En Index, creamos variable con todos los elementos y la devolvemos en formato JSON

 public function index()
    {
        //recuperamos todos los elementos
        $notes = Notes::all();
        //ahora ya no devolvemos una vista (retun view) si no que directamente enviamos esos datos mediante un formato de API RESTFUL con JSON
        return response()->json($notes, 200 );
          /*  JSON Le devolvemos:
                //datos
                //mensaje de estado, 200 = todo OK
                //valor de cabecera (opcional)
          */

    }

Create

Podemos usar las reglas de validación anteriores con la NoteRequest más específica

NoteRequest

Vamos a generar una NoteRequest específica en lugar de la Request genérica para definir qué y cómo queremos recibir los datos, esto no es obligatorio pero si recomendable para separar las validaciones de nuestro controlador

php artisan make:request NoteRequest

Y la genera en la ruta:

   INFO  Request [app/Http/Requests/NoteRequest.php] created successfully.
  1. NoteRequest:

Ruta: [app/Http/Requests/NoteRequest.php]

Cambiamos a true la función authorize:

return true; //Lo cambiamos a true 

Y customizamos la petición en las reglas::

 public function rules(): array

    {
        return [
            'title' => 'required|max:255|min:3', //pedimos un minimo, maximo y que sea requerido el título
            'content' =>'nullable|max:255|min:3' //pedimos un minimo, maximo y que sea requerido el título
        ];
    }

IMPORTANTE: Indicar la librería en el controlador

use App\Http\Request\NoteRequest;

Una vez hecho esto, ¿Cómo se almacena?

Función store (create),

En el caso de una API, la función Create y Store sería el mismo en una api, se usa store, ya que create es para las vistas mostrar un formulario Devolvemos el mensaje correcto, deberíamos de pasarle también el elemento creado por convención pero no lo hace

 public function store(NoteRequest $request)
    {
        //
        Note::create($request->all()); 
        return response()->json([
            'success' => true  
        ],201); // El estado 201 corresponde a la creación correcta de u nuevo elemento
    }

Función show

Busca un elemento con su ID y lo devuelve

   public function show(string $id)
    {
        //Busca un elemento con su ID y lo devuelve
        $note= Note::find($id);
        return response()->json($note, 200);
    }

Función update

Al igual que create, la función edit no es necesaria en API.

Cambiamos la request por NoteRequest

    public function update(NoteRequest $request, string $id)
    {
        $note= Note::find($id);
        //podemos usar el Note::update
        //O para ver la opción directa, lo hacemos:
        $note->title= $request->title;
        $note->content= $request->content;
        $note->save(); // Si lo hacemos así, necesitamos guardar
        return response()->json([
            'success' => true  
        ],204); // El estado 204 corresponde a la creación correcta de u nuevo elemento
    }

Función destroy

 public function destroy(string $id)
    {
        //Buscamos el elemento a borrar
        $note= Note::find($id);
        return response()->json([
            'success' => true  
        ],200); // El estado 204 corresponde a la creación correcta de u nuevo elemento
    }

Ya la hemos definido todas las funciones, ahora vamos a ver cómo mejorar nuestro controlador de recurso.

Tipado En Controlador Y Prueba De Funcionamiento (enlace)

Refactorización sobre el controlador construido, hay dos enfoques:

  • mostrado del tipado de las acciones Empecemos por aquí que es la sencilla, vemos una construcción extensa de cada valor devuelto, comentarios con return...
  • Cambia a usar el tipado con el nuevo formato:
/**
* Display a listing of the resource.
*/ 

Y lo cambiamos por lo siguiente sin comentarios, así con el resto:

 public function index():JsonResponse

Añadiendo las siguientes librerias al controlador:

use Illuminate\Http\JsonResponse;

Construcción más limpia. También puede mejorar el contenido en una sola línea con:

 return response()->json(Note::all(), 200 );

Si bien quita algo de documentación ,queda un código más límpio

Transformación formato de respuesta

Lo siguiente es el control de la respuesta.Podría ser interesante modificar el contenido que quiero devolver

  • tipo de respuesta que vamos a ofrecer
  • Podemos verlo más adelante con los api resources, añadiendo más contenido, ocultando...

Primero vamos a comprobarla Por convención, se devuelve el recurso que se acaba de crear;

Así que añade a la respuestas de creación y actualización para devolver el recurso y facilitar la vida al cliente.

 'data' => $note

Peticiones de cliente (Thunder Client)

Haremos la prueba con la extensión VSC Thunder Client, si bien existen otras conocidas como Postman.

Vamos a probarlo, crearemos una colección en esta extensión para probar la APi Lo primero, arrancamos el servicio

 php artisan serve 

https://youtu.be/DvzLf1SMASo?t=2683 Probamos todos los endpoints..

En la pestaña lateral izquierda, pulsamos sobre ThunderClient y creamos una new Request (Símbolo rayo)

  • Incluimos el tipo de petición (POST)
  • Añadimos la ruta hacia /api/note http://localhost:8000/api/note
  • Admitimos tipo de contenido Json

1738078796302

y en Body, añadimos en formato JSON los datos que vamos a solicitar crear una nota:

{
  "title":"Hello World",
  "content": "Lorem Impsun"
}

Vemos cómo lo hace bien, probamos el resto

1738079068998

Index (GET)

1738079218394

Update (PUT)

1738079756236

  • Posible mejora AL DEVOLVER EL UPDATE, lo hace bien pero no lo muestra

delete

1738079883616

Collections para Almacenar peticiones en ThunderClient

Es muy útil guardar las diferentes peticiones creadas con ThunderClient para su posterior prueba.

1738080004611

php artisan route:list

Es importante apreciar los diferentes verbos de una API Rest y cómo hay rutas como api/note que hacen acciones diferentes con verbos diferentes aún usando la misma ruta como api/note

1738297695600

PROTECTED HIDDEN

En el modelo, especificamos los campos que NO queremos mostrar

  protected $hidden =['created_at', 'updated_at'];

17382983928851738298425967

Ahora al probar el index (GET /api/note) , ya no nos devuelve esos campos en la petición Ya controlamos cómo vamos a devolver los datos

¿Cómo puyedo modificar los datos para devolverlos?

Si están relacionados, cómo devolvemos el elemento completo y no su ID??

Para eso están los API REsources, que establecen capa intermedia para que los modelos construyan los datos antes de devolverlos y pder así "cocinarlos"

Guarda tu v1

Para tener una mejor trazabilidad de lo realizado, puedes optar por hacer una copia de tu proyectoapicrud para poder ver después, ya que vamos a realizar cambios a las funciones de su controlador.

Api Resources (enlace)

Como hemos dicho, es muy interesante modificar el formato de lo que vamos a devolver,

  • pensemos por ejemplo en una clave foránea, yo no quiero devolver un ID de la clave, quiero devolver el elemento relacionado.
  • O hay datos que si no das algo más de contexto, no se comprenden

Por tanto, vamos a realizar lo anterior mediante un archivo Resource, para ello ejecutamos

php artisan make:resource NoteResource

Esto generará una nueva carpeta y una nueva clase en app\Http\Resources\NoteResource

1738298916954

1 NoteResource

  • Vamos a trabajar con esta clase para crear peticiones personalizadas a las que teníamos anteriormente.
  • Por defecto devuelve los elementos de la tabla, podemos comentar la línea que viene.

Si nos fijamos en el ejemplo hemos añadido un campo example que no está definido en el modelo de Note, pero aún así funcionará y cuando hagamos una consulta saldrá también este campo.

    class NoteResource extends JsonResource{
        public function toArray($request): array
        {
            return [
                'id' => $this ->id,
                'title' => 'Title: ' . $this->title,
                'content' => $this->content,
                'example' => 'This is an example'
            ];
        }
    }

2 NoteController

  • Una vez hecho lo anterior debemos irnos a nuestro controlador desarrollado anteriormente y debemos importar lo siguiente:
use App\Http\Resources\NoteResource;
use Illuminate\Http\Resources\Json\JsonResource;

Con esto podremos trabajar con respuestas Resource, con lo que vamos a devolver colleciones de los recursos. Modificamos las funciones de nuestro controlador

* Funcion Index

  • Hay que incluir la librería use Illuminate\Http\Resources\Json\JsonResource;
    public function index():JsonResource  //Hemos cambiado de tipo devuelto, de JsonResponse a JsonResource
    {

        return NoteResource::collection(Note::all());
    }

1738299493428

* Funcion Store

  • Aunque vamos a devolver una JsonResponse, vamos a modificar e incluir el data para NoteResource
    public function store(NoteRequest $request):JsonResponse
    {
        $note   = Note::create($request->all());
        return response()->json([
            'success' => true,
            'data' => new NoteResource($note)
        ],201); // El estado 201 corresponde a la creación correcta de u nuevo elemento
    }
  • Funcion Show

Aquí igual, vamos a devolver ahora una JsonResource:

* FunciónUpdate

Aquí, como en el Store, cuando devolvemos el data, ahora devolvemos el note Resource:

    public function update(NoteRequest $request, string $id):JsonResponse
    {
        $note= Note::find($id);
        $note->title= $request->title;
        $note->content= $request->content;
        $note->save(); // Si lo hacemos así, necesitamos guardar
        return response()->json([
            'success' => true,
            'data' => new NoteResource($note)
        ],204);
    }

Comprobar con cliente ThunderClient/Postman

Si hemos implementado correctamente lo anterior deberían hacerse peticiones correctamente y al ejecutarn por ejemplo el index vemos que nos devuelven Notas con el campo example.

Conclusiones

Aquí ya somos capaces de devolver datos tanto en vista como en API.