Como controlar los request, el formato de la informacion a JSON y editar la respuesta de error de una API en Laravel 5.6

in utopian-io •  7 years ago  (edited)

Repository

https://github.com/laravel/laravel

Que aprenderé ?

  • Controlar el numero de peticiones por minutos a nuestra API en Laravel
  • Editar el mensaje de error que viene por defecto con Laravel
  • Crear un Trait para la mejor lectura y eficiencia de codigo en nuestra API

Requerimientos

  • Editor de codigo (en mi caso uso PHP Storm)
  • Servidor Xampp/Lampp (Depende de tu sistema operativo)
  • Consola de comando
  • API tester, como POSTMAN

Dificultad:

  • Intermedia

Contenido

Muchas veces nos encontramos, que cuando estamos creando una API en laravel, el mensaje que deja por defecto cuando un usuario pasa el limite de peticiones al servidor es algo soso y no deja mucha información, ademas si estamos creando una API lo que mas queremos es que la misma respuesta sea enviada en un formato ideal para una API (JSON en este caso) asi que con este tutorial se plantea una solución a este problema de forma que sea sencilla de leer para futuras consultas

comenzando con la instalación de nuestro proyecto fresco, asi que nos vamos a la consola y escribimos

composer create-project --prefer-dist laravel/laravel nombre-proyecto

donde "nombre-proyecto" puede ser el que ustedes quieran, configuran todo su entorno en el archivo .env y estamos listos para arrancar

Para efectos de este tutorial nos vamos a dirigir a la carpeta "database" y ubicamos el archivo DatabaseSeeder.php
y dentro del metodo run colocamos lo siguiente

public function run()  
{  
  factory(User::class, 15)->create();  
}

y luego nos vamos a la consola y escribimos

php artisan migrate --seed

¿que quiere decir esto?
le estamos diciendo a laravel que nos cree la migraciones de las tablas que trajo por defecto y nos haga un seed con 15 usuarios en la tabla de usuarios.

Ahora que ya tenemos nuestros usuarios creados, queremos acceder a una lista de ellos en formato JSON, para ellos nos vamos a registrar las rutas en el archivo api.php en la carpeta Routes

Route::resource('users', 'UsersController')->only(['show', 'index']);

y creamos nuestro controlador

php artisan make:controller UsersController -r

¿que fue lo que logramos con todo esto?
Registramos nuestras rutas para que usaran el controlador "UsersController" y que solo usara los metodos "index" y "show" tal como muestra la imagen

route list.PNG

ahora nos ubicamos sobre el controlador "UsersController.php" y vamos a escribir
en el metodo index

public function index()  
{  
  $users = User::all();  
  return response()->json(['data' =>$users]);  
}

esto quiere decir que cada vez que hagamos un consulta a la ruta /api/users , nos retornara un objeto JSON que a su vez contiene un objeto "data" el cual tiene todos los usuarios como por ejemplo

lista usuarios.PNG

Ahora vamos con el metodo "show" para mostrar un solo registro en nuestra API, nos volvemos a ubicar en el controlador y colocamos lo siguiente:

public function show($id)  
{  
  $user = User::find($id);  
  return response()->json(['data' => $user]);  
}

y si hacemos una consulta a la ruta /api/users/1, nuestra API nos arroja el siguiente resultado:
showone.PNG

pero ahora ¿que pasaria si el registro no existe en nuestra base de datos?
laravel por defecto coloca el resultado como null tal como se expresa aca:
null.PNG

Pero ¿como hacemos si queremos arrojar un mensaje de error diciendo que no se encontró ningún resultado?
Hay dos maneras de manejar esto

  • la mas ideal es dejar que el programador que trabaje Front-end sea quien personalice el mensaje cuando el resultado sea null.
  • arrojar mediante nuestra API un mensaje personalizado diciendo que no se puede encontrar el registro.

vamos con la segunda opción:
Ahora modificando un poco la funcion "show" agregamos lo siguiente

public function show($id)  
{  
  $user = User::find($id);  
  
  if ($user == null)  
  {  
     return response()->json(['error' => 'Usuario no encontrado', 'code' => 404], 404);  
  }  
     return response()->json(['data' => $user]);  
}

de esta manera cuando se haga una peticion a la API y esta no encuentre un resultado, arrojara lo siguiente:
404.PNG

Pero ahora, si analizamos un poco el código, vemos que hay patron que se repite y hace que nuestro código sea un poco mas difícil de leer para otros, el cual es

return response()->json()

y tal vez si queremos agregar mas resultados esto nos va traer mas código asi, por eso en este tutorial aprenderemos a abstraer esta parte por algo mas legible, creando un controlador que extienda todos los métodos del Controlador principal de laravel y agregando nuestros métodos abstractos al mismo, haciendo uso de nuestro nuevos metodos en los controladores de nuestra API

comenzando creando un trait en la ruta app/Traits (si no existe la carpeta Traits, solo creala) llamado ApiResponser.php el cual llevara el siguiente contenido

<?php  
  
namespace App\Traits;  
  
use Illuminate\Database\Eloquent\Model;  
use Illuminate\Support\Collection;  
  
trait ApiResponser  
{  
  private function succesResponse($data, $code)  
 {  
     return response()->json($data, $code);  
 }  
  protected function errorResponse($message, $code)  
 {  
     return response()->json(['error' => $message, 'code' => $code], $code);  
 }  
  protected function showAll(Collection $collection, $code = 200)  
 {  
     return $this->succesResponse(['data' => $collection], $code);  
 }  
  protected function showOne(Model $instance, $code = 200)  
 {  
     return $this->succesResponse(['data' => $instance], $code);  
 }
}
 

creamos nuesto nuevo controlador mediante la consola:

php artisan make:controller ApiController

y ahora nos ubicamos sobre nuestro nuevo controlador (debe estar en la ruta app\Http\Controllers) el cual se encuentra vacio, el cual solo vamos agregar una sola linea de codigo:

<?php  
  
namespace App\Http\Controllers;  
  
use App\Traits\ApiResponser;  
use Illuminate\Http\Request;  
  
class ApiController extends Controller  
{  
  use ApiResponser;  //esta es la linea que agregamos
}

y reescribimos nuestro UsersController de la siguiente manera

<?php  
  
namespace App\Http\Controllers;  
  
use App\User;  
use Illuminate\Http\Request;  
  
class UsersController extends ApiController  //Ahora extiende de nuestro nuevo controlador y usa los nuevos metodos que escribimos en el Trait
{  
  /**  
 * Display a listing of the resource. 
 * *@return \Illuminate\Http\Response  
 */  
 public function index()  
 {  
     $users = User::all();  
     return $this->showAll($users);  //funciona igual que nuestro metodo anterior, pero ahora es mas legible su funcionalidad
 }  
  /**  
 * Display the specified resource. * * @param int  $id  
 * @return \Illuminate\Http\Response  
 */  
 public function show($id)  
 {  
     $user = User::find($id);
     if ($user == null)  
     {  
     return $this->errorResponse('Usuario no encontrado', 404);  //Al no encontrar un registro arrojara este error para que sea manejado por el front de manera adecuada
     }  
     return $this->showOne($user);  //Muestra el registro encontrado
 }  
}

Ya con estos metodos tenemos una manera facil de escribir codigo mas legible y tenemos mas control sobre las respuestas de nuestra API.

Ahora vamos agregar un poco de seguridad al middleware "throttle" de la API, dicho middleware lo encontraremos registrado en la ruta app\Http en el archivo Kernel.php bajo el objeto $routedMiddleware

/**  
 * The application's route middleware groups. * * @var array  
 */
 protected $middlewareGroups = [  
  'web' => [  
  \App\Http\Middleware\EncryptCookies::class,  
  \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,  
  \Illuminate\Session\Middleware\StartSession::class,  
  // \Illuminate\Session\Middleware\AuthenticateSession::class,  
  \Illuminate\View\Middleware\ShareErrorsFromSession::class,  
  \App\Http\Middleware\VerifyCsrfToken::class,  
  \Illuminate\Routing\Middleware\SubstituteBindings::class,  
 ],  
  'api' => [  
  'throttle:60,1',  // Justo aqui esta registrado el middleware para la API para un limite de 60 peticiones por minuto
  'bindings',  
    ],
 ];  
  
/**  
 * The application's route middleware. * * These middleware may be assigned to groups or used individually. 
 * 
 * @var array  
 */
 protected $routeMiddleware = [  
  'auth' => \Illuminate\Auth\Middleware\Authenticate::class,  
  'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,  
  'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,  
  'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,  
  'can' => \Illuminate\Auth\Middleware\Authorize::class,  
  'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,  
  'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,  
  'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,  //Registra el nombre de el middleware throttle
];

ahora ¿que pasa cuando un usuario rebasa el limite peticiones?
laravel arroja el siguiente error formateado en HTML y dandole sus propios estilos:
too many request.PNG

este no es el formato ideal de respuesta para una API y laravel no provee una manera directa de modificar este comportamiento en su documentación, así que manos a la obra.

La manera mas rapida y directa es verificando cual es la instancia que se ejecuta cuando se llega al limite de peticiones establecido, en el caso de laravel 5.6 la instancia que se ejecuta es ThrottleRequestsException la cual no podemos modificar, mas si podemos aprovechar el uso del método render en el archivo app/Exceptions/Handler.php
de la siguiente manera:

<?php  
  
namespace App\Exceptions;  
  
use App\Traits\ApiResponser;  
use Exception;  
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;  
use Illuminate\Http\Exceptions\ThrottleRequestsException;  
  
class Handler extends ExceptionHandler  
{  
  use ApiResponser;  //Hacemos uso de nuestro trait de respuestas
    ....

  /**  
 * Render an exception into an HTTP response. 
 * 
 * @param \Illuminate\Http\Request  $request  
 * @param \Exception  $exception  
 * @return \Illuminate\Http\Response  
 */  public function render($request, Exception $exception)  
     {  
         if ($exception instanceof ThrottleRequestsException) {  
         return $this->errorResponse('limite de peticiones rebasado', 409); //Aqui estamos modificando el comportamiento por defecto de la aplicacion y enviamos el mensaje modificado para el uso apropiado en nuestra API  
         }  
         if (config('app.debug')) {  
         return parent::render($request, $exception);
         } 
     }
}

ya con esto verificamos en nuestra API que se muestra el mensaje:
limite rebasado.PNG

y ya con esto estamos listos para desarrollar una API con todas las respuestas en un formato deseado, espero les haya gustado y ayudado este tutorial y hasta la próxima
.

Curriculum

Proof of Work Done

https://github.com/ErSulba/ApiResponser

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Thank you for your contribution.
This moderation might not be considered due to the below:

  • Only submissions in easily understood English will be reviewed and scored. Contributions in other languages must include a full and comprehensible translation of all content included in the submission.

Please read the guidelines here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Oh sorry, i've tought you were still moderating tutorials in other language outside English like my first one.

Tell me, do i have to make another post, or just edit this one with the translation?

Please make a new contribution from a new subject.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]