Crear API RESTful con laravel

Índice
  1. Crear un controlador con todos los métodos
  2. Rutas o ENDPOINTS para comunicarse con la API
  3. Crear rutas para registro de usuarios
  4. Controlar ediciones del propio usuario
  5. Reducir tiempo de expiración para tokens
  6. Cambio de estilos css en al API

Crear un controlador con todos los métodos

Laravel permite crear un controlador con todos los métodos o funciones de la forma más fácil, sin tener que escribirlos desde cero.

Ejecutar en línea de comandos la siguiente secuencia

php artisan make:controller Api\CategoryController --api --model=Category

  • make:controller, crea el controlador.
  • Api\CategoryController, es la ruta donde se crea, carpeta y nombre
  • --api, para indicar la naturaleza del controlador.
  • --model=Category, para asignarle un modelo por ejemplo el de Category.

Metodos generados

Automáticamente se han generado los siguientes métodos o funciones necesarias para trabajar con API:

A diferencia con la creación de un controlador para realizar un CRUD, en el que se pasaría el parámetro -r, no se crea el método create() ni tampoco el método edit($id). Esto es porque en una API no se necesita devolver una vista, simplemente devuelve respuestas a peticiones.

Método index

En el método index se suele devolver todos los registros de la tabla en cuestión.

public function index()
    {
        $categories = Category::all();
        return $categories;
    }

Método store

Agrear regla de validación con método validate a la solicitud a través del request recibido por parte del cliente.

public function store(Request $request)
    {

        $request->validate([
            'name' => 'required|max:255',
            'name' => 'required|max:255|unique:categories',
        ]);


        $category = Category::create($request->all());

        return $category;
    }

Si pasa las validaciones crea el registro en la base de datos y devuelve la información al cliente.

Importante en la creación masiva es que el modelo tenga habilitada la asignación masiva, bien por inclusión (fillable) o bien por exclusión (guarded). Ver el detalle de estas dos formas en la sección Crear modelo en Laravel.

protected $fillable = ['name', 'slug'];

Método show

Este método se encarga de recoger el identificador del registros a través de la petición web y buscarlo en la base de datos.

Ejemplo de llamada donde 1 es el identificador

http://api.test/categories/1

Si lo encuentra lo devuelve con todos sus datos, o bien si no lo encuentra devuelve información de no encontrado

 public function show(Category $category)
    {
        return $category;
    }

Método update

El método store se utiliza para actualizar los datos de un registro. Los datos reciben a través del método PUT, se validan y si son aceptado se guardan.

En la validación se indica que no se considere el ID del registro, para así permitir actualizar un registro existente y no diga que el slug ya existe (slug,' . $category->id)

public function update(Request $request, Category $category)
{
 $request->validate([
  'name' => 'required|max:255',
  'slug' => 'required|max:255|unique:categories,slug,' . $category->id,
  ]);

  $category->update($request->all());

  return $category;
}

En las validaciones no se deben poner espacios, porque puede dar errores.

update(Request $request, Category $category)
destroy(Category $category)

Puedes ver aquí como crear un controlador para CRUDLaravel crud controller.

Método delete

Con este método se recibe la petición de tipo DELETE con el indentificador y se borra el registro de la base de datos. Devuelve el registro borrado.

public function destroy(Category $category)
    {
        $category->delete();

        return $category;
    }

Rutas o ENDPOINTS para comunicarse con la API

A destacar en las rutas para una API es que se diferencia de las rutas web, en que va controlado por un middleware distinto.

Ficheros de rutas

El fichero routes\web.php controla el envío de formulario con la directiva de seguridad @csrf, que evita vulnerabilidades como la inyeción de código en formularios.

El fichero routes\api.php va controlado por el middleware de 'api', que no lleva ese control @csrf.

Crear rutas manualmente

Crear una ruta general de registro:

Route::post('register', [RegisterController::class, 'store'])->name('api.register');

Se necesita crear rutas para acceder a cada uno de los métodos creados. Entonces en el fichero de rutas crear una para cada método.

Route::post('register', [RegisterController::class, 'store'])->name('api.register');
Route::get('categories', [CategoryController::class, 'index'])->name('api.categories.index');
Route::post('categories', [CategoryController::class, 'store'])->name('api.categories.index');
Route::get('categories/{category}', [CategoryController::class, 'show'])->name('api.categories.show');
Route::put('categories/{category}', [CategoryController::class, 'update'])->name('api.categories.update');
Route::delete('categories/{category}', [CategoryController::class, 'delete'])->name('api.categories.delete');

Crear rutas automáticamente y de forma simplificada

Todo el código anterior se simplifica en Laravel con utilizando el método apiResource:

Route::apiResource('categories', CategoryController::class)->names('api.v1.categories');

Donde se le indica el prefijo, la clase y el alias de las rutas. Indicar que el alias se escribe en plural porque son varias rutas y no una ->names('prefijoderuta').

Verificar rutas creadas

En linea de comandos ejecutar el siguiente comando para visualizar las rutas generadas.

php artisan r:l

Para visualizar unas rutas especificas utilizar --name. Por ejemplo para visualizar solo las rutas con passport (permisos)

php artisan r:l --name=passport

Instalar Laravel permisos con Passport es muy fácil y solo hay que seguir unos sencillos pasos para disfutrar de este control de permisos en Laravel.

Crear rutas para registro de usuarios

Para que los clientes se puedan autenticar en la API, hay que habilitarles la ruta adrministrada por un controlador.

Crear un controlador para la ruta login

Es el encargado de gestionar el login de los clientes que se conecten a la API.

php artisan make:controller Api\Auth\LoginController

Este comando genera el fichero LoginController.php dentro de la carpeta Api

app\Http\Controllers\Api\Auth\LoginController.php

Abrir el fichero para crear un método llamado store, para implementar las validaciones y control de acceso.

  • Regla de validación
  • Acceso al modelo User con filtro. En vez de utilziar first(), firstOrFail utilizar si no existe informa que no esta.
  • Importar Hash, comprueba password recibida y la cifrada en base de datos.
  • UserResource, para devolver como recurso con UserREsource, y unificar todos los tipos de respuesta.
  • Enviar response en formato json y mensaje 404.
use Illuminate\Support\Facades\Hash;
use App\Http\Resources\UserResource;

...

public function store(Request $request){

 //regla validacion
 $request->validate([
            'email' => 'required|string|email',
            'password' => 'required|string'
 ]);

 $user = User::where('email', $request->email)->firstOrFail();

 if(Hash::check($request->password, $user->password)){

   return UserResource::make($user);
 }
 else{

 return response()->json(['message', 'These credentials do not match our records.',404]);
 }
}

Crear una ruta para login

En este ejemplo se va a llamar login, y se va a agregar al fichero de rutas de la API routes\api.php

Route::post('login', [LoginController::class, 'store'])->name('api.login');

Proteger rutas por scopes

Los scopes permiten controlar lo que el cliente puede hacer o no puede hacer, por ejemplo desde no poder modificar un nombre de una access token, hasta limitar los permisos del access token. Llevado a la práctica, se puede entregar un acceso a un programador para que solamente pueda realizar acciones que se le han encomendado.

Definir el método tokensCan

Para ello abrir el fichero AuthServiceProvider

app\Providers\AuthServiceProvider.php

Definir los tipos de permisos que se van a controlar en el método tokensCan

Passport::tokensCan([
 'create-product' => 'Crear un producto',
 'read-product' => 'Leer un producto',
 'update-product' => 'Actualizar un producto',
 'delete-product' => 'Eliminar un producto',
]);

Además se puede configurar un scope por defecto, para que en caso de no asignación lo tome predefinido, y se hace con el método setDefaultScope.

Passport::setDefaultScope([
 'read-product'
]);

Registrar el middleware

Abrir el fichero Kernel de la carpeta HTTP, y cuidado que no es el kernel de la carpeta Console

app\Http\Kernel.php

Importar las librerías CheckScopes y CheckForAnyScope

...

use Laravel\Passport\Http\Middleware\CheckScopes;
use Laravel\Passport\Http\Middleware\CheckForAnyScope;

..

Localizar el método routeMiddleware y agregar la nueva definición de scopes y scope

protected $routeMiddleware = [

...

 'scopes' => CheckScopes::class,
 'scope' => CheckForAnyScope::class,

];

Una vez definidos ya se puede utilizar para proteger rutas.

Asignar scopes al controlador

En el controlador, hay que asignarle los scopes definidos en el AuthServiceProvider con la función middleware, conforme se desee proteger.

En el ejemplo se protege la lectura, creación, actualización y borrado de un producto. Lo tachado es porque después se va a modificar añadiendo control de permisos del paquete Spatie

app\Http\Controllers\Api\ProductController.php

 $this->middleware('scopes:read-product')->only(['index','show']);
 $this->middleware('scopes:create-product')->only(['store']);
 $this->middleware('scopes:update-product')->only(['update']);
 $this->middleware('scopes:delete-product')->only(['delete']);

Además modificar permisos a nivel de usuarios con Laravel Permisos de Spatie a través del método can:

 $this->middleware(['scopes:create-product', 'can:create products'])->only(['store']);
 $this->middleware(['scopes:update-product', 'can:edit products'])->only(['update']);
 $this->middleware(['scopes:delete-product', 'can:delete products'])->only(['delete']);

Controlar ediciones del propio usuario

Para que un usuario no pueda modificar registros que no sean suyos, se utilizan Policies.

Crear un Policy a un modelo en concreto con este comando, y para ejemplo de un modelo Product

php artisan make:policy ProductPolicy --model=Product

Esto genera un fichero en una carpeta Policies

app\Policies\ProductPolicy.php

Abrir el fichero e implementar una función nueva para controlar el author del registro llamada author

public function author(User $user, Product $product){
 if($product->user_id == $user->id){
  return true;
 }
 else{
  return false;
 }
}

Aplicar esto a cada método del controlador donde se desea restringir

Por ejemplo se necesita controlar la actualización y borrado de productos, entoces en el controlador ProductController se llama al método de la policy, justo antes de las validaciones

app\Http\Controllers\Api\ProductController.php

...

public function update(Request $request, Product $product)
{
 $this->authorize('author', $product);

...
...

public function destroy(Product $product)
{
 $this->authorize('author', $product);

...

Con esto, si el usuario quisiera editar o borrar un producto que no es suyo, Laravel le va a impedir el acceso.

Reducir tiempo de expiración para tokens

Para mejorar la seguridad, se puede reducir el tiempo por defecto que es de un año.

Para ello abrir el fichero AuthServiceProvider

app\Providers\AuthServiceProvider.php

Agregar el siguiente código en el método boot para calcular el tiempo actual y el tiempo de expiración, a través de la librería de Permisos Passport. En el ejemplo caducará en 60 segundo, aunque también se puede poner otros intervalos, etc. (addDays, addHours, addMinutes, addSeconds)

...

public function boot()
{
 $this->registerPolicies();

 Passport::routes();
 Passport::tokensExpireIn(now()->addSeconds(60));

...

}

Cambio de estilos css en al API

Puede que el paquete passport venga con el paquete de estilos de bootstrap, y no se muestre bien, ya que la API está con estilos Tailwind.

Es facil cambiarlo, publicando las vistas y modificando el CDN

En la API (no en el cliente actual) ejecutar el comando vendor:publish y se le pasa las vistas a publicar

php artisan vendor:publish --tag=passport-views

Este copia los directorios y ficheros privados a la carpeta pública

Carpeta origen: \vendor\laravel\passport\resources\views
Carpeta destino: \resources\views\vendor\passport

Después de publicar los fichero s hay que limpiar y compilar de nuevo con los comandos

php artisan clear-compiled
composer dumpautoload

Abrir entonces el fichero authorize para cambiar el CDN de tailwind a bootstrap, para el ejemplo la versión 5.

resources\views\vendor\passport\authorize.blade.php

{{--<link href="{{ asset('/css/app.css') }}" rel="stylesheet">--}}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir