Introducción

1

¿Ya terminaste el Curso de NestJS: Programación Modular?

2

Platzi Store: presentación del proyecto e instalación

Database

3

Cómo instalar Docker para este proyecto

4

Configuración de PostgresSQL en Docker

5

Explorando postgres con interfaces gráficas y terminal

6

Integración de node-postgres con NestJS

7

Conexión como inyectable y ejecutando un SELECT

8

Usando variables de ambiente

TypeORM

9

¿Qué es un ORM? Instalando y configurando TypeORM Module

10

Creando tu primera entidad

11

TypeORM: active record vs. repositories

12

Crear, actualizar y eliminar

13

Cambiar a Mysql demo (opcional)

Migraciones

14

Sync Mode vs. Migraciones en TypeORM

15

Configurando migraciones y npm scripts

16

Corriendo migraciones

17

Modificando una entidad

Relaciones

18

Relaciones uno a uno

19

Resolviendo la relación uno a uno en el controlador

20

Relaciones uno a muchos

21

Resolviendo la relación uno a muchos en el controlador

22

Relaciones muchos a muchos

23

Resolviendo la relación muchos a muchos en el controlador

24

Manipulación de arreglos en relaciones muchos a muchos

25

Relaciones muchos a muchos personalizadas

26

Resolviendo la relación muchos a muchos personalizada en el controlador

Consultas

27

Paginación

28

Filtrando precios con operadores

29

Agregando indexadores

30

Modificando el naming

31

Serializar

Migración a NestJS 9 y TypeORM 0.3

32

Actualizando Dependencias para NestJS 9

33

Cambios en TypeORM 0.3

34

Migraciones en TypeORM 0.3

Próximos pasos

35

Cómo solucionar una referencia circular entre módulos

36

Continúa con el Curso de NestJS: Autenticación con Passport y JWT

You don't have access to this class

Keep learning! Join and start boosting your career

Aprovecha el precio especial y haz tu profesión a prueba de IA

Antes: $249

Currency
$209
Suscríbete

Termina en:

1 Días
17 Hrs
58 Min
9 Seg

Relaciones uno a uno

18/36
Resources

How to manage relationships in relational databases with TypeORM?

In the world of relational databases, it is crucial to understand how to manage the different relationships that can occur between tables, such as one-to-one, one-to-many, and many-to-many. In this class we will explore how TypeORM can be used to manage these relationships, focusing on the one-to-one relationship, specifically between a user and a customer.

How is the one-to-one relationship established?

We are going to create the necessary entities, following good practices in structuring them:

  1. Customer entity:

    • Primary key(ID).
    • Attributes such as name, lastName, and phone, all of Varchar type.
    • Timestamps for created_at and updated_at.
  2. Entity User:

    • Primary key(ID).
    • Attributes such as email, password (encrypted in future implementations), and role.
    • Timestamps for created_at and updated_at.

Both entities must be declared in the User Module, where they will be configured as entities managed by TypeORM.

How to create the one-to-one relationship?

To implement the one-to-one relationship, two TypeORM decorators must be imported and used: @OneToOne and @JoinColumn. Our relationship, in this scenario, will be optional, allowing some users to have no associated clients (administrative users, for example).

// In User Entity@OneToOne(() => CustomerEntity, { nullable: true })@JoinColumn()customer: CustomerEntity;

We use @OneToOne to declare the relationship and @JoinColumn exclusively on one of the entities, designating the entity "owner" of the relationship.

How to handle bidirectional relationships?

TypeORM supports bidirectional relationships, which means that any entity can know its associated counterpart:

// In Customer Entity@OneToOne(() => UserEntity, user => user.customer, { nullable: true })user: UserEntity;

Here we specify the @OneToOne decorator with a row function, resolving the relationship from both entities. However, @JoinColumn is only specified on one side, in this case on User, to establish who has the relationship in the database.

How to create and execute migrations?

To establish our entities in the database, it is essential to generate and execute migrations:

  1. Create migration:

    npm run typeorm migration:generate -- create-user-and-customer
  2. Execute migration:

    npm run typeorm migration:run

These actions translate into the physical creation of the tables and relationships in the database. You can verify the execution by accessing pgAdmin and reviewing the generated schemas.

How to check table structures from the terminal?

You can inspect the structures and relationships created by TypeORM by connecting directly to the PostgreSQL container:

docker-compose exec postgres-container bash
psql -h localhost -d mydb -U root
 \d+ users // Query table details users

This methodology allows you to verify the structure of the tables and their foreign keys in detail.

Dare to implement this configuration in your projects, experimenting with the powerful features TypeORM offers you to manage complex relationships in your databases. Keep exploring and learning new tools to take your development skills to the next level!

Contributions 9

Questions 4

Sort by:

Want to see more contributions, questions and answers from the community?

Buenas, si no quieren colocar a cada entity los campos createAt y updateAt pueden crear una entidad básica que contenga estos atributos:

import { CreateDateColumn, UpdateDateColumn } from 'typeorm';

export class BasicEntity {
  @CreateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP',
  })
  createAt: Date;

  @UpdateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP',
  })
  updateAt: Date;
}

Y luego extender cada entidad que necesite estos campos:

import { BasicEntity } from 'src/database/base.entity';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User extends BasicEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'varchar', length: 255 })
  email: string;

  @Column({ type: 'varchar', length: 255 })
  password: string;

  @Column({ type: 'varchar', length: 100 })
  role: string;
}

De esta forma nos ahorramos un poco de tiempo al crear nuestras entidades

Vi un comentario que como haría para evitar estar repitiendo los campos de CreateAt y UpdateAt sin usar un herencia de clase, investigando un poco me tope con Embedded Entities. Dejo mi ejemplo el cual me funcionó.

  • Creé un archivo el cual la puse dentro de database.module: “dateAt.entity.ts”
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';

export class DateAt {
  @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createAt: Date;

  @UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  updateAt: Date;
}
  • En las entidades solo importe este archivo y dentro de column realize una arrow function hacia esta nueva clase.
import { PrimaryGeneratedColumn, Column, Entity } from 'typeorm';
import { DateAt } from '../../database/dateAt.entity';

@Entity()
export class Customer {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'varchar', length: 100 })
  name: string;

  @Column({ type: 'varchar', length: 100 })
  lastName: string;

  @Column({ type: 'varchar', length: 20 })
  phone: string;

  @Column(() => DateAt)
  register: DateAt;
}

Me funcionó, incluso corrí el comando npm run migrations:generate -- dateAt, el cual no me dio problema alguno.

Apuntes

Para realizar relaciones 1 a 1 debemos hacer lo siguiente

// src/user/entities/user.entity
@OneToOne(()=>Customer, (customer)=>customer.user, {nullable: true})
@JoinColumn()
customer: Customer;

// src/user/entities/customer.entity
@OneToOne(()=>User, (user)=> user.customer)
user:User;

La entidad que tenga el decorador @JoinColumn es el objeto que creara la llave foranea. Solo una entidad puede tener el decorador @JoinColumn

TypeORM permite tener una relacion bidireccional en las relaciones 1 a 1 sin necesidad de hacer queryes extras.

Lo que hicimos

//src/user/entities/customer.entity
import { Column, CreateDateColumn, Entity, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { User } from "./user.entity";

@Entity()
export class Customer {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({type: 'varchar', length: 255})
  name: string;

  @Column({type: 'varchar', length: 255})
  lastName: string;

  @Column({ type: 'varchar', length: 255 })
  phone: string;

  @CreateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP',
  })
  createAt: Date;

  @UpdateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP',
  })
  updateAt: Date;

  @OneToOne(()=>User, (user)=> user.customer)
  user:User;
}
//src/user/entities/user.entity
import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { Customer } from "./customer.entity";

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'varchar', length: 255})
  email: string;
  
  @Column({ type: 'varchar', length: 255})
  password: string;

  @Column({ type: 'varchar', length: 255})
  role: string;

  @CreateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP'
  })
  createAt: Date;

  @UpdateDateColumn({
    type: 'timestamptz',
    default: () => 'CURRENT_TIMESTAMP',
  })
  updateAt: Date;

  @OneToOne(()=>Customer, (customer)=>customer.user, {nullable: true})
  @JoinColumn()
  customer: Customer;

}
//src/user/services/customer.service
import { Injectable, NotFoundException } from '@nestjs/common';

import { Customer } from '../entities/customer.entity';
import { CreateCustomerDto, UpdateCustomerDto } from '../dtos/customer.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class CustomersService {
  constructor(
    @InjectRepository(Customer) private customerRepo: Repository<Customer>
  ){}

  async findAll() {
    return await this.customerRepo.find()
  }

  async findOne(id: number) {
    const customer = await this.customerRepo.findOne(id);
    if (!customer) {
      throw new NotFoundException(`Customer #${id} not found`);
    }
    return customer;
  }

  create(data: CreateCustomerDto) {
    const newCostumer = this.customerRepo.create(data);
    return this.customerRepo.save(newCostumer);
  }

  async update(id: number, changes: UpdateCustomerDto) {
    const customer = await this.findOne(id);
    this.customerRepo.merge(customer, changes);
    return this.customerRepo.save(customer);
  }

  remove(id: number) {
    return this.customerRepo.delete(id);
  }
}
//src/user/services/user.service
import { Injectable, NotFoundException, Inject } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

import { User } from '../entities/user.entity';
import { Order } from '../entities/order.entity';
import { CreateUserDto, UpdateUserDto } from '../dtos/user.dto';

import { ProductsService } from './../../products/services/products.service';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User) private userRepo: Repository<User>,
    private productsService: ProductsService,
    private configService: ConfigService,
  ) {}

  async findAll() {
    return await this.userRepo.find();
  }

  async findOne(id: number) {
    const user = await this.userRepo.findOne(id);
    if (!user) {
      throw new NotFoundException(`User #${id} not found`);
    }
    return user;
  }

  async create(data: CreateUserDto) {
    const newUser = this.userRepo.create(data);
    return await this.userRepo.save(newUser);
  }

  async update(id: number, changes: UpdateUserDto) {
    const user = await this.findOne(id);
    const updateUser = this.userRepo.merge(user, changes);
    return this.userRepo.save(updateUser);
  }

  remove(id: number) {
    return this.userRepo.delete(id);
  }

  async getOrderByUser(id: number): Promise<Order> {
    const user = await this.userRepo.findOne(id);
    return {
      date: new Date(),
      user,
      products: await this.productsService.findAll(),
    };
  }
}
npm run migrations:generate -- create-user-customer
npm run migrations:run

Relaciones uno a uno

Para manejar las relaciones debemos especificarlo directamente desde nuestras entidades, para entender como manejar las relaciones uno a uno vamos a tomar como caso a user.entity.ts y a customer.entity.ts, vamos a lograr que los usuarios tengan como parámetro opcional un customer.

  • **src/user/entities/user.entity**:
import {
  // ...
  OneToOne,
  JoinColumn,
} from 'typeorm';

// importamos la entidad a la cual queremos relacionarnos
import { Customer } from './customer.entity';

@Entity()
export class User {
  // ...

  // manejamos la referencia la entidad con OneToOne
  // especificamos con una función flecha que resuelva la entidad que le indiquemos
  // el customer es opcional "{ nullable: true }"
  @OneToOne(() => Customer, { nullable: true })
  // este decorador crea la referencia con una llave foranea
  @JoinColumn()
  costomer: Customer;
}

Relaciones bidireccionales

TypeORM nos la la posibilidad de trabajar las relaciones de forma bidireccional. Lo que significa que le podemos comunicar también al customer que el esta relacionado con un usuario:

  • **src/user/entities/customer.entity**:
import {
  // ...
  OneToOne,
} from 'typeorm';

// importamos al usuario
import { User } from './user.entity';

@Entity()
export class Customer {
  // ...

  // especificamos que resuleva la relación con el usuario
  // especificamos cual es el campo que referencia la relación, esto para tener comunicación bidireccional
  @OneToOne(() => User, (user) => user.customer, { nullable: true })
  user: User;
}

Ahora en el usuario, debemos indicar que esperamos recibir la comunicación con customer:

  • **src/user/entities/user.entity**:
import {
  Column,
  Entity,
  UpdateDateColumn,
  CreateDateColumn,
  PrimaryColumn,
  OneToOne,
  JoinColumn,
} from 'typeorm';

// importamos la entidad a la cual queremos relacionarnos
import { Customer } from './customer.entity';

@Entity()
export class User {
  // ...

  // con una función flecha le indicamos que esperamos una relación bidireccional con customer
  // le indicamos cual es el campo de customer que resuelve la relaión
	@OneToOne(
		() => Customer, 
		(customer) => customer.user, // <--
		{ nullable: true }
	)
  // este decorador crea la referencia con una llave foranea
  @JoinColumn()
  customer: Customer;
}

Es importante tener en cuenta que al establecer una relación bidireccional entre dos entidades en TypeORM, solo debemos especificar la referencia en una de las dos entidades. Esto significa que, si queremos establecer una relación bidireccional, solo una de las entidades tendrá el decorador @JoinColumn(), el cual será responsable de definir la clave foránea. En este caso, la entidad que contiene la referencia es la entidad “User”.

Ahora solo debemos correr una migración y con eso crearemos nuestra primera relación uno a uno.

Comandos para acceder a la base de datos desde docker en la terminal:

$ docker-compose exec postgres bash
$ psql -h localhost -d my_db -U root
$ \d+
$ \d+ user
$ \d+ customer

Para salir deben ejecutar exit 2 veces.

jajajaj los momentos de los ajustes a 4x son los mejores

Podemos configurar ONDELETE y ONUPDATE en nuestras entidades de la siguiente manera:

  @OneToOne(() => Customer, (customer) => customer.user, {
    nullable: true,
    onDelete: 'SET NULL',
    onUpdate: 'CASCADE',
  })
  @JoinColumn()
  customer: Customer;

Recomiendo hacer una entidad base con id, updateAd y createAd. Para que luego extender todas las demas entidades de esta base.
Ej:

import {
  CreateDateColumn,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

export abstract class BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @CreateDateColumn({
    type: 'timestamp',
    name: 'created_at',
  })
  createdAt: Date;

  @UpdateDateColumn({
    type: 'timestamp',
    name: 'updated_at',
  })
  updatedAt: Date;
}