TypeORM y NestJS forman una sólida combinación para gestionar bases de datos relacionales.
TypeORM permite definir entidades, relaciones y realizar consultas, mientras que NestJS facilita la creación de repositorios y migraciones. Juntos, ofrecen una solución completa y eficiente para el manejo de datos en aplicaciones NestJS.
Antes de comenzar con la creación de entidades y tablas, es necesario configurar TypeORM en un proyecto NestJS.
Creación de entidades
Las entidades en TypeORM representan las tablas en la base de datos y se definen usando clases y decoradores.
Ejemplo
Crear una entidad "Usuario".
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('usuarios') // Define la clase como entidad y le da un nombre a la tabla en DB
export class Usuario {
@PrimaryGeneratedColumn() // Columna primaria auto-generada (ej. auto-incremento)
id: number;
@Column() // Columna regular
nombre: string;
@Column({ unique: true }) // Columna regular con restricción de unicidad
email: string;
@Column({ default: true }) // Columna con un valor por defecto
isActive: boolean;
}
@Entity('usuarios')
: Este decorador marca la claseUsuario
como una entidad y especifica que se mapeará a una tabla llamadausuarios
en tu base de datos. Si no proporcionas un nombre, TypeORM usará el nombre de la clase en minúsculas.@PrimaryGeneratedColumn()
: Define una columna que será la clave primaria de la tabla y cuyo valor se generará automáticamente por la base de datos (por ejemplo, un auto-incremento). Puedes especificar una estrategia si lo necesitas, como@PrimaryGeneratedColumn('uuid')
para IDs UUID.@Column()
: Define una columna regular en tu tabla. Puedes pasarle un objeto de opciones para configurar propiedades adicionales comounique: true
,default
,nullable
,length
,type
, etc.
Uso de relaciones entre entidades
TypeORM soporta múltiples relaciones entre entidades:
- Uno a Uno (
OneToOne
) - Uno a Muchos (
OneToMany
) - Muchos a Uno (
ManyToOne
) - Muchos a Muchos (
ManyToMany
)
En TypeORM, las relaciones son bidireccionales. Esto significa que defines la relación en ambas entidades. Los decoradores de relación (@OneToOne
, @OneToMany
, etc.) toman dos argumentos:
- Una función que devuelve el tipo de la entidad relacionada (ej.,
() => Perfil
). Se usa una función de flecha para evitar problemas de referencia circular cuando las entidades se importan mutuamente. - Una función que devuelve la propiedad en la entidad relacionada que apunta de vuelta a la entidad actual (ej.,
perfil => perfil.usuario
). Esto define la relación inversa.
Relación OneToOne
Una relación OneToOne
significa que una entidad se relaciona con otra entidad y ambas solo pueden tener una relación entre sí.
Ejemplo:
Supongamos que se tienen dos entidades: Usuario
y Perfil
. Cada usuario tiene un perfil y cada perfil pertenece a un único usuario.
Código de la entidad Usuario
:
// usuario.entity.ts
import { Entity, PrimaryGeneratedColumn, OneToOne, JoinColumn } from 'typeorm';
import { Perfil } from './perfil.entity'; // Importa la entidad Perfil
@Entity()
export class Usuario {
@PrimaryGeneratedColumn()
id: number;
@OneToOne(() => Perfil, perfil => perfil.usuario) // Relación con Perfil, la inversa es 'perfil.usuario'
@JoinColumn() // Indica que Usuario es la propietaria de la relación y contendrá la clave foránea (ej. perfilId)
perfil: Perfil; // Propiedad que guardará el objeto Perfil relacionado
}
Código de la entidad Perfil
:
// perfil.entity.ts
import { Entity, PrimaryGeneratedColumn, OneToOne } from 'typeorm';
import { Usuario } from './usuario.entity'; // Importa la entidad Usuario
@Entity()
export class Perfil {
@PrimaryGeneratedColumn()
id: number;
@OneToOne(() => Usuario, usuario => usuario.perfil) // Relación con Usuario, la inversa es 'usuario.perfil'
usuario: Usuario; // Propiedad que guardará el objeto Usuario relacionado
}
Relación OneToMany
y ManyToOne
OneToMany
y ManyToOne
se utilizan juntas.
Ejemplo: Usuario
y Tarea
(Un usuario puede tener muchas tareas, pero cada tarea pertenece a un único usuario)
Entidad Usuario
:
// usuario.entity.ts
import { Entity, PrimaryGeneratedColumn, OneToMany } from 'typeorm';
import { Tarea } from './tarea.entity'; // Importa la entidad Tarea
@Entity()
export class Usuario {
@PrimaryGeneratedColumn()
id: number;
@OneToMany(() => Tarea, tarea => tarea.usuario) // Relación con Tarea, la inversa es 'tarea.usuario'
tareas: Tarea[]; // Propiedad que guardará un array de objetos Tarea
}
Entidad Tarea
:
// tarea.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { Usuario } from './usuario.entity'; // Importa la entidad Usuario
@Entity()
export class Tarea {
@PrimaryGeneratedColumn()
id: number;
@Column()
descripcion: string;
@ManyToOne(() => Usuario, usuario => usuario.tareas) // Relación con Usuario, la inversa es 'usuario.tareas'
usuario: Usuario; // Propiedad que guardará el objeto Usuario relacionado (y su clave foránea)
}
Relación ManyToMany
Esta relación se utiliza cuando múltiples registros de una entidad pueden estar relacionados con múltiples registros de otra entidad. TypeORM creará automáticamente una tabla intermedia (o de unión) para gestionar esta relación.
Ejemplo: Estudiante
y Curso
(Un estudiante puede inscribirse en muchos cursos, y un curso puede tener muchos estudiantes)
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Entidad Estudiante
:
// estudiante.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable } from 'typeorm';
import { Curso } from './curso.entity'; // Importa la entidad Curso
@Entity()
export class Estudiante {
@PrimaryGeneratedColumn()
id: number;
@Column()
nombre: string;
@ManyToMany(() => Curso, curso => curso.estudiantes) // Relación con Curso, la inversa es 'curso.estudiantes'
@JoinTable() // Indica que Estudiante es la propietaria de la relación y creará la tabla de unión
cursos: Curso[]; // Propiedad que guardará un array de objetos Curso
}
Entidad Curso
:
// curso.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm';
import { Estudiante } from './estudiante.entity'; // Importa la entidad Estudiante
@Entity()
export class Curso {
@PrimaryGeneratedColumn()
id: number;
@Column()
titulo: string;
@ManyToMany(() => Estudiante, estudiante => estudiante.cursos) // Relación con Estudiante, la inversa es 'estudiante.cursos'
estudiantes: Estudiante[]; // Propiedad que guardará un array de objetos Estudiante
}
Repositorios y manipulación de datos
Una vez que las entidades están definidas y las relaciones establecidas, NestJS y TypeORM ofrecen mecanismos para manipular y consultar la base de datos a través de repositorios.
Repositorios
Un repositorio es una clase que permite realizar operaciones CRUD (crear, leer, actualizar y eliminar) sobre una entidad.
Para cada entidad, TypeORM crea automáticamente un repositorio.
Ejemplo: Para acceder a un repositorio en un servicio de NestJS, utiliza el decorador @InjectRepository()
:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; // Importa la clase Repository de TypeORM
import { Usuario } from './usuario.entity'; // Importa tu entidad Usuario
@Injectable()
export class UsuarioService {
constructor(
@InjectRepository(Usuario) // Inyecta el repositorio de la entidad Usuario
private usuarioRepository: Repository<Usuario>, // Tipa correctamente el repositorio
) {}
// Obtener todos los usuarios
findAll(): Promise<Usuario[]> {
return this.usuarioRepository.find();
}
// Obtener un usuario por ID (¡Actualizado para TypeORM 0.3.x!)
findOne(id: number): Promise<Usuario | null> { // Añade | null para seguridad de tipos
return this.usuarioRepository.findOne({ where: { id } });
}
// Crear un nuevo usuario
create(usuarioData: Partial<Usuario>): Promise<Usuario> {
const newUser = this.usuarioRepository.create(usuarioData);
return this.usuarioRepository.save(newUser);
}
// Eliminar un usuario por ID
async remove(id: number): Promise<void> {
await this.usuarioRepository.delete(id);
}
}
Consultas personalizadas
Los repositorios ofrecen métodos básicos para operaciones CRUD, pero en casos más complejos, se pueden construir consultas personalizadas utilizando el Query Builder de TypeORM.
Ejemplo: Buscar usuarios activos con un nombre específico.
// Dentro de UsuarioService
async findActiveByName(nombre: string): Promise<Usuario[]> {
return this.usuarioRepository.createQueryBuilder('usuario') // 'usuario' es el alias para la tabla
.where('usuario.nombre = :nombre', { nombre }) // Cláusula WHERE para el nombre
.andWhere('usuario.isActive = :isActive', { isActive: true }) // Otra cláusula WHERE
.getMany(); // Ejecuta la consulta y obtiene múltiples resultados
}
Migraciones
A medida que tu aplicación evoluciona, la estructura de tus entidades (y por lo tanto, de tus tablas de base de datos) cambiará.
Las migraciones son la forma segura y controlada de gestionar y rastrear estos cambios en el esquema de la base de datos, especialmente en entornos de producción.
¡ATENCIÓN! Como se mencionó anteriormente, synchronize: true
es peligroso en producción. Siempre desactívalo y usa migraciones.
Para utilizar migraciones, primero, es necesario modificar tu configuración de TypeORM en el TypeOrmModule.forRoot()
(usando forRootAsync
):
// En tu AppModule (o donde tengas TypeOrmModule.forRootAsync)
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
// ... otras configuraciones (type, host, port, etc.) ...
entities: [__dirname + '/**/*.entity{.ts,.js}'],
// ** Desactivar synchronize en producción **
synchronize: false, // <-- ¡Importante!
// Configuración para las migraciones
migrations: [__dirname + '/../migrations/*{.ts,.js}'], // Ruta a tus archivos de migración
// logging: true, // Útil para ver las consultas de TypeORM en la consola
}),
inject: [ConfigService],
})
Además, a menudo necesitarás una configuración específica de TypeORM en un archivo ormconfig.ts
o directamente en package.json
para que la CLI de TypeORM pueda generar y ejecutar las migraciones.
Ejemplo de un ormconfig.ts
(en la raíz de tu proyecto):
import { DataSource } from 'typeorm';
import * as dotenv from 'dotenv';
dotenv.config(); // Cargar variables de entorno
const AppDataSource = new DataSource({
type: process.env.DB_TYPE as any || 'mysql',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '3306', 10),
username: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_DATABASE || 'nest_db',
entities: ['dist/**/*.entity{.ts,.js}'], // Rutas a tus entidades compiladas
migrations: ['dist/migrations/*{.ts,.js}'], // Rutas a tus migraciones compiladas
synchronize: false, // Asegúrate de que esté en false aquí también
migrationsRun: false, // No ejecutar migraciones automáticamente al iniciar
});
export default AppDataSource;
Luego, puedes generar y ejecutar migraciones utilizando la CLI de TypeORM:
- Generar una nueva migración:
npx typeorm migration:create ./src/migrations/NombreDeTuMigracion
- Ejecutar migraciones pendientes:
npx typeorm migration:run
- Revertir la última migración:
npx typeorm migration:revert
Conclusión
La integración de NestJS con TypeORM facilita enormemente la creación y manejo de entidades y tablas en la base de datos. Las entidades representan la estructura de tus datos, mientras que los decoradores te permiten definir sus características y las relaciones entre ellas. Con el uso adecuado de repositorios para las operaciones CRUD y las migraciones para el control de versiones del esquema de tu base de datos, tendrás una solución robusta y escalable para la gestión de datos en tus aplicaciones NestJS.
Aprendizajes de esta lección
- Comprender el concepto de entidades y tablas en TypeORM.
- Aprender a crear entidades y tablas.
- Conocer cómo funcionan las relaciones entre entidades.
- Entender cómo se utilizan los repositorios para manipular datos.
- Conocer cómo funcionan las migraciones en TypeORM.
Completa Nest y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs