Qué son los repositorios de TypeORM, cómo usar repositorios dentro de Nest Framework, inyectando cómodamente las clases en los servicios. Ejemplos de accesos y modificaciones de registros en la base de datos en NestJS usando el ORM.
Los repositorios son una de las piezas de software que están implementadas en el lado de TypeORM y que sirven para proveer de las funcionalidades típicas de acceso a los datos y, por supuesto, las operaciones de escritura en las tablas.
Los repositorios están limitados al trabajo con una entidad particular. Por ejemplo, el repositorio de "product" trabajará con los productos.
Una vez tengamos el repositorio "product" podemos invocar sobre él los métodos como findOne()
, save()
, insert()
y muchos otros, todos disponibles gracias a TypeORM.
Incluso podemos agregar métodos a los repositorios por medio de la realización de un "custom repository", del que podemos ver más detalles más adelante o en la documentación de TypeORM.
Los repositorios los podemos conseguir generar de manera explícita mediante el API de TypeORM, pero Nest ya nos facilita las cosas, aportando la posibilidad de inyectar repositorios de una entidad en los servicios donde lo necesitemos.
Cómo usar repositorios en NestJS
En el artículo anterior dedicado a las entidades ya explicamos que en los módulos que implementan una entidad necesitamos usar el método forFeature()
de TypeOrmModule
, indicando el nombre de la entidad.
Esto es todo lo que necesitamos para poder usar un repositorio en un servicio, pero antes debemos configurar el propio servicio inyectando el repositorio que necesita.
Inyección de repositorios en un servicio
Como hemos dicho, una vez hemos definida una entidad en ese módulo, somos capaces de inyectar el repositorio de esa entidad en un servicio. Para ello vamos a usar un nuevo decorador que nos proporciona la integración de Nest con TypeORM, llamado @InjectRepository()
.
Para poder usarlo vamos a necesitar importar dos declaraciones en el servicio:
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
Una es el decorador que acabamos de mencionar y otra es la propia clase Repository
que nos ofrece TypeORM.
Nuestro servicio ahora requiere la inyección del repositorio de producto, que vamos a realizar en el constructor.
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product)
private productsRepository: Repository<Product>,
) {}
}
Como puedes apreciar, en el constructor se declara una propiedad privada para este servicio llamada productsRepository
. Esta propiedad obtendrá el repositorio de producto, algo que conseguimos:
- Usando el decorador
@InjectRepository
que se encarga de instanciar el repositorio de la entidad enviada por parámetro. - Tipando la propiedad con la clase
Repository
y el genérico de aquella entidad que pretendemos usar.
Ahora ya estamos en condiciones de usar nuestro repositorio de producto dentro del servicio.
Invocando los métodos del repositorio en un servicio
Hasta ahora nuestro servicio de producto hacía uso de la memoria para almacenar los datos. Esto ya lo podemos cambiar y empezar a trabajar con bases de datos!! Para ello simplemente debemos ir invocando los métodos adecuados del repositorio inyectado en el servicio.
Por ejemplo, si queremos seleccionar elementos de la tabla usamos el método find()
del repositorio.
Así nos quedaría el método getAll()
del servicio. Ahora no devuelve el array que teníamos en memoria, sino que accede a los productos mediante find()
del objeto productsRepository
.
getAll() {
return this.productsRepository.find();
}
Como puedes imaginar, si no enviamos parámetros al método find()
nos devuelve todos los elementos de una entidad.
Existe un método del repositorio que podemos usar para conseguir un único elemento, llamado findOne()
, que usamos en el método getId()
. En el caso de findOne()
tenemos que enviarle el identificador que permite seleccionar un elemento de la entidad de manera única, es decir, su clave primaria.
getId(id: number): Promise<Product> {
return this.productsRepository.findOne(id);
}
Aparte de consultas para obtener datos de una entidad, con los repositorios podemos hacer todo tipo de operaciones, como crear elementos de la entidad o editarlos. Los vamos a usar en los próximos métodos.
Este sería el método que permite insertar un producto en la tabla de la entidad de producto.
async insert(body: ProductDto) {
const product = this.productsRepository.create(body);
await this.productsRepository.save(product);
return product;
}
Este método tiene varios puntos que deben considerarse:
- El método requiere dos pasos, primero crear el recurso y el segundo salvarlo en la base de datos.
- El método de creación,
create()
recibe los datos con los que el elemento de la entidad se tiene que construir. Sin embargo, este método no lo almacena en la base de datos. - Para almacenar los datos del producto creado usamos ``````save()``` pasando como parámetro aquel producto que deseamos salvar.
- Por último, devolvemos el producto una vez salvado.
Es además clave entender por qué lo hemos tenido que firmar como async
. Dado que la operación de salvado del producto es asíncrona, y queremos enviar como valor de retorno del método el producto salvado, necesitamos aplicar un await
en la línea del método save()
. La especificación async
la colocamos porque todo método que usa await
debe firmarse como async
.
Ahora podemos ver otro método del servicio, que hace el guardado de un dato que se desea actualizar.
async update(id: number, body: any) {
const userProduct = {
id,
...body,
}
const product = await this.productsRepository.preload(userProduct);
if(product) {
return this.productsRepository.save(product);
}
throw new NotFoundException(`No se encuentra el producto ${id}`);
}
En este caso necesitamos actualizar un elemento de la base de datos. Esta operación la realizamos con el método preload()
del repositorio. Hay algunos puntos que nos debemos fijar:
- Primero construyo un objeto con los datos del producto que tengo y quiero guardar. Ese objeto lo construyo montando un objeto llamado
userProduct
, al que le ponemos el id con el valor del identificador recibido y los datos del body de la solicitud. - Luego usamos
preload()
enviando como parámetro eluserProduct
. Este método hace dos cosas. Primero recupera de la base de datos el elemento con el identicador que concuerde con el objeto que queremos precargar. Sobre el elemento que se ha recuperado aplica todas las propiedades que vienen en el objeto que enviamos al métodopreload()
. - Esta operación
preload()
es asíncrona, porque obtener los datos de un producto es algo asíncrono. Una vez acabada tendremos un objeto de producto que mezclará los datos del producto con el id buscado, más los datos del body que nos han enviado para actualizar. - Sin embargo, si el identificador del producto no se encontró, devolverá vacío. Así que lo que hacemos es comprobar si se tiene un producto o no con el
if
. - Si se tiene un producto, simplemente se asegura de salvar su nuevo estado en la base de datos, y devolver el producto una vez guardado.
- Si no se encontró un producto al hacer el preload, entonces simplemente se envía un error 404.
Igual que en el método anterior, este método es imporante declararlo como asíncrono para poder hacer uso de await
dentro.
Por último, el método de borrado se ve de esta manera:
async delete(id: number) {
const product = await this.productsRepository.findOne(id);
if (product) {
return this.productsRepository.remove(product);
}
throw new NotFoundException(`No se encuentra el producto ${id}`);
}
- El método debe ser asíncrono, ya que el borrado implica primero recuperar un elemento de la tabla de la entidad (aquel que queremos borrar). Como este primer paso es asíncrono, tenemos que esperar a resolverlo antes de continuar con el resto del método.
- Una vez que se tiene el elemento que se desea borrar, entonces se ejecuta
remove()
del repositorio. - Si no se encontró lo que se pretendía borrar, entonces se levanta una excepción.
Así es como hemos definido, método a método, el servicio, incorporando el acceso a la base de datos a todas las operaciones que ofrece.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...