Nicolas Molina
ProfesorJosué Eliezer Gómez Soto
Josué Eliezer Gómez Soto
En las relaciones embebidas usamos las siguientes dos formas:
Para relaciones 1:1 embebidas:
@Prop( raw({ name: { type: String }, image: { type: String }, }), ) category: Record<String, Any>;
Para relaciones 1:N embebidas:
@Prop({ type: [{ name: { type: String }, color: { type: String } }], }) skills: Types.Array<Record<String, Any>>;
Sin embargo en los dos usamos any lo cual hace que no tengamos un tipado en estos documentos embebidos, por eso vamos a ver otra forma de lograrlo.
La solución
Lo primero es que vamos a crear un esquema para ese documento embebido, de esta manera:
// src/products/entities/sub-doc.entity.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; @Schema() export class SubDoc { @Prop() name: String; @Prop() description: String; } export const SubDocSchema = SchemaFactory.createForClass(SubDoc);
Con su respectivo DTO:
// src/products/dtos/sub-doc.dto.ts import { IsString, IsNotEmpty } from 'class-validator'; import { PartialType } from '@nestjs/swagger'; export class CreateSubDocDto { @IsString() @IsNotEmpty() readonly name: String; @IsString() @IsNotEmpty() readonly description: String; } export class UpdateSubDocDto extends PartialType(CreateSubDocDto) {}
Luego importamos este SubDoc dentro de la entidad que queramos que maneje la relación, así:
// src/products/entities/product.entity.ts import { SubDoc, SubDocSchema } from './sub-doc.entity'; // 👈 import ... export class Product extends Document { ... @Prop({ type: SubDocSchema }) subDoc: SubDoc; // 👈 new field (1:1) @Prop({ type: [SubDocSchema] }) subDocs: Types.Array<SubDoc>; // 👈 new field (1:N) } ...
Debes tener en cuenta que este SubDoc no va a estar declarado en el módulo, ya que es un documento embebido y no una colección.
Con esto ya tienes el objeto embebido con tipado y además lo puedes incluir en tus DTOS para una validación más poderosa, de la siguiente manera:
// src/products/dtos/products.dtos.ts import { ..., IsArray } from 'class-validator'; import { Type } from 'class-transformer'; // 👈 transform import { CreateSubDocDto } from './sub-doc.dto'; // 👈 import export class CreateProductDto { ... @IsNotEmpty() @ValidateNested() readonly subDoc: CreateSubDocDto; // 👈 1:1 @IsNotEmpty() @IsArray() @ValidateNested({ each: true }) @Type(() => CreateSubDocDto) readonly subDocs: CreateSubDocDto[]; // 👈 1:N }
En este punto puedes crear un producto con un documento embebido de forma tipada y con una validación profunda, puedes ver esta forma en la rama 18 del proyecto por si quieres revisar el código a más detalle.
Si haces un POST te debería resultar algo así:
Nicolas Molina
ProfesorJosué Eliezer Gómez Soto
Josué Eliezer Gómez Soto
Ivonne lizeth Tarapués Rodríguez
Catalano Nicolas
Fernando Cordero
Luis Berenguer
Josué Eliezer Gómez Soto
Segundo Juan
Segundo Juan
Josué Eliezer Gómez Soto
José Luis Cornejo Valencia
👈 En esta lectura está la solución de como lograr las relaciones embebidas incluyendo el tipado.
Pregunta, porque este Schema, la clase no extiende de Document?
Otra pregunta, porque la propiedad usa String, en lugar de solo string:
name: String; vs name string;
Para que Swagger se muestre correctamente se debe agregar:
@ApiProperty({ type: () => [CreateSubDocDto]}) readonly subDocs: CreateSubDocDto[];
De igual manera me parece que los tipos de las propiedades deben ser string en lugar de String, sino Swagger los muestra como objetos.
readonly description: string;
Excelente aporte! Muchas gracias!
Les dejo el codigo para la solucion de la clase anterior:
// src/users/entities/skills.entity.ts import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; @Schema() export class Skill { @Prop() name: string; @Prop() color: string; } export const SkillSchema = SchemaFactory.createForClass(Skill);
// src/users/dtos/skills.dto.ts import { IsNotEmpty, IsString } from 'class-validator'; import { PartialType } from '@nestjs/swagger'; export class CreateSkillDto { @IsString() @IsNotEmpty() readonly name: string; @IsString() @IsNotEmpty() readonly color: string; } export class UpdateSkillDto extends PartialType(CreateSkillDto) {}
Implementacion en el dto de customers
import { CreateSkillDto } from './skills.dto'; export class CreateCustomerDto { ... @IsArray() @IsNotEmpty() @ValidateNested({ each: true }) @Type(() => CreateSkillDto) readonly skills: CreateSkillDto[]; }
y en la entidad de customers
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, Types } from 'mongoose'; import { Skill, SkillSchema } from './skills.entity'; @Schema() export class Customer extends Document { @Prop({ required: true, type: String }) name: string; @Prop({ required: true, type: String }) email: string; @Prop({ type: [SkillSchema], }) skills: Types.Array<Skill>; } export const CustomerSchema = SchemaFactory.createForClass(Customer);
En que momento se debe usar String en lugar de string?
Brother tengo la misma preguta... pero resulta que aquí es dificíl tener una respuesta por más que hablen de la "comunidad"
asi remplazamos el 'raw'?
porque en product.entity importamos SubDoc y SubDocSchema y no el CreateSubDocDto? oesa porque en el @Prop({type: }) usamos el Schema y no el DTO?
A mi no me funciono el tipado, y revise mi código y no tengo todo correcto:
Mi entidad:
import { Prop, SchemaFactory } from '@nestjs/mongoose'; export class Address { @Prop({ type: String, required: true }) department: string; @Prop({ type: String, required: true }) city: string; @Prop({ type: String, required: true }) suburb: string; @Prop({ type: String }) street: string; @Prop({ type: String }) passage: string; @Prop({ type: String }) complement: string; } export const AddressSchema = SchemaFactory.createForClass(Address);
Mi DTO
import { IsNotEmpty, IsString } from 'class-validator'; import { PartialType } from '@nestjs/mapped-types'; export class AddressDTO { @IsString() @IsNotEmpty() readonly department: string; @IsString() @IsNotEmpty() readonly city: string; @IsString() @IsNotEmpty() readonly suburb: string; @IsString() readonly street: string; @IsString() readonly passage: string; @IsString() readonly complement: string; } export class UpdateAddressDTO extends PartialType(AddressDTO) {}
Esto va a la entidad:
import { PartialType } from '@nestjs/mapped-types'; import { Type } from 'class-transformer'; import { AddressDTO } from './address.dto'; import { IsArray, IsEmail, IsNotEmpty, IsNumber, IsString, ValidateNested, } from 'class-validator'; export class CreateCustomerDTO { @IsString() @IsNotEmpty() readonly name: string; @IsString() @IsNotEmpty() readonly lastname: string; @IsString() @IsNotEmpty() readonly password: string; @IsNumber() @IsNotEmpty() readonly phone: number; @IsEmail() @IsNotEmpty() email: string; @IsNotEmpty() @IsArray() @ValidateNested({ each: true }) @Type(() => AddressDTO) readonly address: AddressDTO[]; } export class UpdateCustomerDTO extends PartialType(CreateCustomerDTO) {}
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { Document, Types } from 'mongoose'; import { Address, AddressSchema } from './address'; @Schema() export class Customer extends Document { @Prop({ type: String, required: true }) name: string; @Prop({ type: String, required: true }) lastname: string; @Prop({ type: String, required: true }) password: string; @Prop({ type: Number, required: true }) phone: number; @Prop({ type: String, required: true }) email: string; @Prop({ type: [AddressSchema] }) address: Types.Array<Address>; } export const CustomerSchema = SchemaFactory.createForClass(Customer);
POST para agregar productos con documentos embebidos.