Crear API RESTful con laravel
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/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
Deja una respuesta