Entidades del ORM en aplicaciones NestJS. Veremos el concepto de entity de TypeORM, cómo declarar entidades y cargarlas fácilmente desde el módulo principal y los módulos que las necesiten. Mejorar la configuración de Nest para bases de datos.
El ORM TypeORM funciona con un concepto llamado entidad, o en inglés "Entity", el cual define un tipo de recurso con el cual vamos a trabajar en una aplicación, que se asociará directamente con una tabla de una base de datos, o una colección en el caso que se trabaje con MongoDB.
El concepto en sí es el mismo que usamos cuando trabajamos con bases de datos en general, solo que en el contexto de un ORM justamente las entidades son las que hacen la magia, para realizar automáticamente el mapeo directo de los objetos a registros en las tablas de la base de datos.
En este momento estamos en el Manual de Nest, pero realmente el concepto de entidad y cómo crearlos es algo relativo a TypeORM y no a NestJS en particular.
Cómo definir una entidad
Como otros elementos de NestJS, las entidades de TypeORM son clases que debemos decorar con una anotación, en este caso @Entity
.
import { Entity } from "typeorm";
@Entity()
export class User {
}
Dentro de la clase, en el código de la entidad, se definen las propiedades de un recurso, con sus tipos de datos, a los que ahora vamos a añadir un nuevo decorador, que indicará que esa propiedad se corresponde con una columna que se va a crear en la tabla.
Hasta ahora los tipos los habíamos definido en simples interfaces, que nos servían para ayudarnos con TypeScript durante la generación del código, pero ahora los queremos usar también para definir columnas de una base de datos y necesitamos decorar nuestras interfaces, por lo que ya no pueden ser interfaces sino clases.
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class Product {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
stock: number;
}
Las entidades se mapean en tablas, las cuales deben tener una clave primaria. Por ello, el campo identificador lo hemos decorado con @PrimaryGeneratedColumn
.
El resto de campos se pueden definir simplemente con @Column
y el propio ORM podrá simplemente decidir el tipo de datos de columna que se creará en la tabla a partir del tipo que hemos indicado mediante TypeScript.
Tal como hemos definido la entidad, más adelante nuestra tabla, una vez la cree el ORM, tendrá esta forma:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| description | varchar(255) | NO | | NULL | |
| stock | int(11) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
Las entidades las podemos situar en cualquier carpeta del proyecto. Lo más aconsejado desde el punto de vista del dominio es definirlas lo más cercanas posible a los archivos o módulos que trabajan con ellas. Sin embargo, también hay desarrolladores que prefieren colocar todas las entidades juntas en una carpeta común.
Registrar las entidades en la conexión con la base de datos
Todas las entidades que van a generarse en la aplicación deben ser reconocidas por TypeORM y para ello en el objeto de datos para el acceso a la base de datos tenemos que indicar dónde están las entidades.
import { Product } from './products/entities/product.entity';
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
//...
entities: [Products]
});
Definir las entidades en los módulos que las van a usar
En los módulos de Nest debemos indicar que vamos a usar las entidades y para ello tenemos que declararlas en el array "imports
" del decorador @Module()
. Este trabajo lo hacemos mediante el mismo módulo que usamos para conectarnos con la base de datos, TypeOrmModule
, que ya llegamos a usar en el módulo principal.
Sin embargo, ahora usaremos un método distinto. Si en el módulo principal usábamos forRoot()
, en el módulo que usa esa entidad vamos a usar forFeature()
.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Product } from './entities/product.entity';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';
@Module({
imports: [TypeOrmModule.forFeature([Product])],
controllers: [ProductsController],
providers: [ProductsService]
})
export class ProductsModule {}
Por supuesto, ahora en ProductsModule
tenemos que importar TypeOrmModule
y Product
de la carpeta entities de este mismo módulo.
Esto nos permitirá inyectar el repositorio de productos dentro de ProductsService
, pero esta parte la vamos a ver un poco más adelante.
Además, si planeamos usar esta entidad desde otros módulos, necesitaremos exportar TypeOrmModule
.
@Module({
imports: [TypeOrmModule.forFeature([Product])],
controllers: [ProductsController],
providers: [ProductsService],
exports: [TypeOrmModule]
})
Opciones útiles a la hora de configurar TypeOrmModule
Existen algunas opciones de configuración extra para TypeOrmModule
, que pueden ahorrarnos algunas tareas. Vamos a ver un par de ellas interesantes, aunque en la documentación encontrarás más opciones, ya sean creadas por TypeORM directamente o como añadidos por el framework Nest.
Estas configuraciones las colocamos en las propiedades del objeto de conexión con la base de datos, que enviamos al método forRoot()
del TypeOrmModule
, cuando lo usamos en el módulo principal.
Sincronizar las entidades con tablas de la base de datos
Hasta el momento no se ha creado la tabla de productos en la base de datos, aunque hemos creado la entidad correspondiente y la hemos registrado en todos los imports de los módulos relacionados.
Para que en la etapa de desarrollo se creen esas tablas definidas en las entidades podemos usar una configuración llamada "synchronize
", que nos permite justamente que se creen las tablas automáticamente, a partir de las definiciones de las entidades.
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
//...
entities: [Products],
synchronize: true,
});
Ahora, si se arranca la aplicación, veremos que se ha generado automáticamente la tabla "Product" en la base de datos.
Importante: Aunque útil en desarrollo, esta opción no es adecuada para entornos en producción, porque podría hacernos perder datos en las tablas.
Cargar automáticamente las entidades
Si lo deseamos, NestJS puede realizar por nosotros el proceso de cargar las entidades en TypeORM, sin que las tengamos que indicar en el array de la propiedad "entities". Para ello usamos la configuración autoLoadEntities
.
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
//...
autoLoadEntities: true,
synchronize: true,
});
Gracias a que cada módulo define las entidades que va a trabajar mediante forFeature()
, Nest es capaz de saber dónde están todas las entidades usadas en la aplicación, con lo que nos podemos ahorrar la necesidad de importarlas en el módulo principal.
Esto es bastante útil para descargar de código al módulo principal, pero también para no tener que hacer el trabajo de importar dos veces la propia entidad, en el módulo principal y en el módulo que la usa.
Tipos para las columnas en las entidades
Antes de acabar este artículo dedicado a las entidades gestionadas por TypeORM, vamos a comentar, aunque sea por alto, que es posible definir los tipos específicos que queremos en las columnas de la bases de datos.
La definición de estos tipos específicos la podemos enviar al decorador @Column()
, indicando el tipo e incluso parámetros de configuración de ese tipo.
Esto nos permite ser más detallistas, seleccionando exactamente el tipo de la columna que se generará en la base de datos. Sin embargo, tenemos que ser cuidadosos porque no todas las bases de datos soportan todos los tipos de columnas. Tipos como "varchar", "int", "boolean" pueden estar en la mayoría de las bases de datos pero otros más específicos como "json" no en todas. Es importante leer la documentación de TypeORM para aclarar estos detalles y los tipos soportados por cada motor de bases de datos.
Por ejemplo, podemos definir que un campo es entero:
@Column('int')
stock: number;
Podemos definir que un campo es "varchar" y que además tiene un tamaño máximo determinado.
@Column('varchar', { length: 50 })
name: string;
También podemos definir la longitud del número entero:
@Column('int', { width: 5 })
stock: number;
En resumen, todas estas configuraciones las soporta el propio ORM, por lo que las encontrarás detalladas en la página de la documentación de TypeORM.
Cómo cambiar el nombre de la tabla en una entidad
Puede ser útil o necesario en ocasiones cambiar el nombre de las tablas que usamos en la base de datos para almacenar los datos de una entidad.
En el decorador @Entity
, que usamos a la hora de especificar la clase de la entidad podemos indicar también como parámetro el nombre de la tabla, de esta manera:
Anteriormente, el nombre de la tabla lo tomaba directamente del nombre de la entidad, por lo tanto la tabla se llamaba "product", en singular. Si nosotros preferimos que el nombre de la tabla lo tome en plural podemos indicarlo así.
@Entity('products')
Por supuesto, los nombres de las tablas pueden ser totalmente arbitrarios, usando cosas como:
@Entity('my_products')
Conclusión
De momento es todo lo que necesitas para comenzar a trabajar con tus entidades en TypeORM y Nest. Ahora sí, estamos a las puertas de comenzar a guardar y recuperar datos en la base de datos, pero para esto tenemos que aprender a usar nuevas piezas del ORM como son los repositorios. Todo esto lo veremos a continuación en el Manual de Nest.
Miguel Angel Alvarez
Fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Com...