Laravel: API Rest con Repository Pattern + DTO Parte 1

Mauricio Flores Hernández
9 min readOct 9, 2020

--

Así es amigos, un post más sobre como implementar el patrón de repositorios en un proyecto Laravel utilizando Data Transfer Objects (DTO). En este post vamos a realizar un simple CRUD de una lista de tareas.

Antes de comenzar a escribir código como locos, es importante mencionar qué es el patrón de repositorio, Eric Evans en su libro Domain-Driven Design, lo define como:

Mecanismo para encapsular el comportamiento de almacenamiento, obtención y búsqueda, de una forma similar a una colección de objetos (si parecida a una lista o arreglo)

En otras palabras, es la capa que encapsula la lógica requerida para acceder a los datos.

Otro concepto que debemos tener claro, son los Objetos de transferencia de datos (DTO), no son más que clases planas que nos permiten estructurar la información y contenerla, de manera que no importe de donde provenga, ya sea de la base de datos o incluso de una petición HTTP. Utilizar DTO nos permite saber siempre con que atributos estamos trabajando.

Lo primero que debemos hacer, será crear un nuevo proyecto Laravel, pueden hacerlo a través del instalador de Laravel o vía Composer con el siguiente comando:

composer create-project --prefer-dist laravel/laravel blog

Para más información pueden revisar la documentación oficial, la versión más actual de Laravel al escribir este post es la 8.8.0, así que el proyecto se creará con dicha versión.

Ahora vamos a crear una migración para crear la tabla to_do en nuestra base de datos, con el siguiente comando:

php artisan make:migration create_to_do_table

La migración nos creará una clase llamada CreateToDoTable con dos funciones: up y down, la función up creará la tabla con los atributos que le especifiquemos dentro, la función down borrará la tabla si es que esta existe. A continuación se muestran los atributos que añadiremos a la tabla.

Antes de ejecutar la migración, debemos configurar nuestro archivo .env con las credenciales del gestor de base de datos de nuestra preferencia, a continuación se muestra la configuración del driver para PostgreSQL.

DB_CONNECTION=pgsql //DriverDB_HOST=127.0.0.1DB_PORT=5432 //PuertoDB_DATABASE=todo //Nombre de la base de datosDB_USERNAME=postgres //UsuarioDB_PASSWORD= //Contraseña

Una vez configurado nuestro archivo .env, ejecutamos la migración con el comando:

php artisan migrate

Lo siguiente que necesitamos realizar, es crear un modelo para manipular los datos de nuestra tabla to_do, para ello ejecutamos el siguiente comando:

php artisan make:model ToDo

Cabe mencionar que en esta nueva versión de Laravel, ya viene en la estructura de directorios una carpeta para los modelos dentro de la carpeta App.

Añadimos los atributos de nuestra tabla en la propiedad fillable del modelo quedando de la siguiente manera:

Procedemos a crear un controlador para poder procesar la información de entrada y salida a nuestra API. Ejecutamos el comando:

php artisan make:controller TodoController --resource

Al añadirle el parámetro “--resource”, Laravel nos generará las siguientes funciones dentro:

  • Index
  • Create
  • Store
  • Show
  • Edit
  • Update
  • Destroy

No utilizaremos las funciones Create ni Edit, por lo que las eliminaremos para mantener un código más limpio.

Ahora vamos a crear nuestra ruta y la apuntamos al controlador que acabamos de crear, para ello abrimos el archivo routes/api.php y le añadimos lo siguiente:

Creación de To Do

Antes de crear una tarea, lo más prudente sería validar la información que entra en nuestro API, para ello crearemos un FormRequestClass donde añadiremos las reglas de validación, utilizamos el siguiente comando:

php artisan make:request StoreTodoRequest

Esto creará el archivo App/Http/Requests/StoreTodoRequest.php que contendrá dos funciones:

  • authorize: Determinará si el usuario está autorizado para hacer esta petición.
  • rules: Aquí se definen las reglas que se aplicarán a la petición.

Para esta post no entraremos en detalles de autorización y permisos, así que solo vamos a cambiar el valor de retorno de la función authorize de false a true y solamente vamos a validar que el campo title sea requerido y no pueda exceder los 100 caracteres, para el campo description serán las mismas reglas, solo que éste no podrá exceder los 250 caracteres. :

Ahora en nuestro controlador TodoController, vamos a modificar el tipo de objeto del parámetro $request en la función store a StoreTodoRequest y añadimos el siguiente código a la función para que quede de esta manera:

Procedemos a realizar una prueba con Postman, pero antes iniciamos el servidor que nos proporciona Laravel para realizar pruebas con el comando:

php artisan serve

Si todo marcha bien, debemos ver en consola un mensaje similar a este:

Starting Laravel development server: http://127.0.0.1:8000

Ahora, en postman creamos una petición de tipo POST a la ruta todo, es decir, a la url http://127.0.0.1:8000/todo de la siguiente manera:

Como podemos percibir, nos arrojó un error, indicando que el controlador al que apuntamos la ruta no existe, no debemos preocuparnos, ya que para solucionarlo, simplemente tenemos que descomentar la línea 29 en el archivo App/Providers/RouteServiceProvider.php

Volvemos a intentar y recibimos un status code 201 que nos indica que se ha creado un recurso en el servidor y en el body de la respuesta podemos notar que viene la información del recurso que se ha creado.

Como podemos observar, nuestra ruta para crear un To-do funciona correctamente. vamos a verificar si la validación de los campos del request funciona correctamente, para ello, en el body del request quitamos el atributo title, la respuesta debe ser un código 422 y un mensaje indicando que no cumplimos con las reglas de los campos del request.

La respuesta no arrojó ningún código de error, simplemente nos redireccionó hacía el home page de Laravel, este no es el comportamiento que deseamos cuando construimos un API, generalmente esperaríamos una respuesta en formato JSON. Para ello, necesitamos modificar el archivo App/Exceptions/Handler.php, dentro de éste archivo podemos manipular las posibles excepciones o errores que pueden ocurrir sobrescribiendo las siguientes funciones:

convertValidationExceptionToResponse()
En esta función, simplemente recibimos de parámetro una excepción del tipo ValidationException y un request. Obtenemos los errores que arrojó la excepción, son las observaciones que se aplicaron a los parámetros de entrada tomando como base las reglas que definimos en nuestro StoreTodoRequest.

render()
Verificamos de que tipo es la excepción que se encontró y si es del tipo ValidationException, retornamos una llamada a la función que sobrescribimos anteriormente.

La función render() es muy importante, ya que en ésta podemos manejar cualquier tipo de excepción que se pudiese presentar en nuestra api. Por ejemplo, si queremos eliminar/actualizar un recurso que no existe, simplemente tendríamos que añadir la siguiente condición para verificar si la excepción es del tipo ModelNotFound:

Probamos nuevamente nuestra petición para comprobar que el manejo de errores tiene el comportamiento que deseamos:

En efecto, ahora como respuesta nos arroja los campos que faltan para que la petición sea válida y un código 422.

Actualización de un To Do

Comúnmente los verbos HTTP utilizados para actualizar o modificar recursos en el servidor son PUT y PATCH. Por lo tanto, ahora haremos una petición de tipo PUT hacia la misma ruta, pero adicionalmente tenemos que añadir el ID del recurso que vamos a actualizar. Nuestra url debe quedar algo como esto:

http://127.0.0.1:8000/api/todo/{id}

Antes de lanzar el request, tenemos que programar esa acción en el controlador en el método update. Realizamos algo similar a la creación del To Do, creamos un nuevo FormRequest para añadir las reglas de los campos que debe llevar la petición.

php artisan make:request UpdateTodoRequest

Nuevamente modificamos la función authorize para que retorne un valor true y en la función rules añadimos lo siguiente:

Como podemos observar se validan los mismos campos que en el FormRequest que creamos anteriormente, solo que para este añadimos el campo is_done que obligatoriamente debe ser un valor del tipo booleano (True o False) y los campos title y description dejaron de ser obligatorios y simplemente se valida su longitud de caracteres.

Ahora editamos el tipo de request para la función update en nuestro controlador y añadimos el siguiente código:

Como podemos notar, a diferencia de la función store, además del request, recibimos el id del dato que se debe actualizar. Entonces, se realiza lo siguiente:

  1. Buscamos el todo a actualizar, la función findOrFail buscará el modelo y si no lo encuentra, arrojará una excepción del tipo ModelNotFound.
  2. Rellenamos el modelo encontrado con los datos que llegan en la petición.
  3. Guardamos los cambios en el modelo.
  4. Retornamos la respuesta con los datos actualizados y un status code 200.

Vamos a verificar que nuestra ruta para actualizar funcione correctamente, para ello, creamos un nuevo request en Postman de tipo PUT a la http://localhost:8000/api/todo/1 y en el body añadimos los campos que vamos a actualizar.

Observamos que obtenemos la respuesta que deseamos, tenemos de respuesta los datos actualizados. Ahora vamos a ver que sucede si tratamos de actualizar un objeto que no existe.

Notamos que el error nos indica que no existe el modelo que deseamos actualizar, ¿Recuerdan la condición que añadimos en la función render() del archivo App/Exceptions/Handler.php? Aquí es donde surte efecto, ya que la excepción fue del tipo ModelNotFound.

Mostrar un Todo

Vamos a realizar la búsqueda de un todo por su id, esto es realmente sencillo gracias a Eloquent, en nuestro controlador, ubicamos la función show() y le añadimos lo siguiente:

Como podrán observar, el código es muy similar al anterior donde actualizamos el todo. Aquí simplemente lo buscamos con la función findOrFail() y lo retornamos en un json.

Para verificar que funcione, lanzamos una petición GET a la misma ruta que hemos estado trabajando, pero le añadimos el id del todo que queremos que nos muestre.

http://localhost:8000/api/todo/{id}

Eliminación de un todo

Para finalizar nuestro CRUD, necesitamos poder eliminar un recurso, es una de las funciones más sencillas puesto que es similar a lo que hicimos en la función show().

Nos ubicamos en la función destroy(), generalmente se encontrará al final de nuestro controlador. Añadimos el siguiente código:

Primero buscamos el Todo que deseamos eliminar, si no existe, sabemos que Laravel arrojará una excepción indicando que el modelo no existe. Si el Todo es encontrado, se elimina y retornamos la información del objeto eliminado en formato json.

Para realizar pruebas, necesitamos realizar una petición tipo DELETE, la url debe ser similar a la que utilizamos para mostrar y actualizar, lo único que cambia es el tipo de petición.

Si volvemos a tratar de eliminar el mismo recurso, el error mostrado indica que no existe ningún registro para ese modelo, lo que está correcto ya que fue eliminado.

Conclusión

Hasta este punto hemos realizado un CRUD totalmente funcional tocando algunos aspectos interesantes del framework como los FormRequest y el manejo de excepciones, pero ese no es el objetivo final, ya que aún nos falta refactorizar un poco el código y aplicar el patrón de repositorios con DTOS. Pero no se preocupen, lo realizaremos en las próximas entregas de esta serie para no hacer muy largo y tedioso este post (y que no se aburran 🙁).

Pueden encontrar el código en éste repositorio

--

--

Mauricio Flores Hernández

Hello My name is Mauricio. I’m a TI engineering and proud Legacy GitKraken Ambassador. Right now I’m working at Backbone Systems as a Full Stack Engineer.