what is correct way to setup my DTO to works with TypeORM and relationships - postgres

112 Views Asked by At

i have two entities that needs to be related as follow: enter image description here one project can have just one type (type of project), but a type can be associated to multiple projects

i have a DTO for the project entity that looks like this:

import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
export class CreateProjectDto {

  @ApiProperty({ description: 'project name', example: 'ToDo' })
  @IsNotEmpty()
  @IsString()
  readonly name: string;

  @ApiProperty({ description: 'type of project', example: 'Personal' })
  @IsOptional()
  readonly type: string;

}

and i have an entity for both:

project entity
import {
  Column,
  CreateDateColumn,
  Entity,
  JoinTable,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { ProjectType } from '../project-type/entities/project-type.entity';

@Entity('projects')
export class Project {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(() => ProjectType, (type) => type.projects)
  type: ProjectType;
}

project-type entity
import { Project } from '@/projects/entities/project.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';

@Entity('project_type')
export class ProjectType {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @ManyToOne(() => Project, (project) => project.type)
  projects: Project[];
}

but my service is getting an error: Type 'CreateProjectDto' is missing the following properties from type 'DeepPartial<Project>[]': length, pop, push, concat, and 29 more

this error gets fixed if i change the DTO from readonly type: string; to readonly type: ProjectType;

but when i try to create a project it will ask me for the set of properties that matches type (id , name) and in the frontend i just have a dropdown that renders the options coming from the types entity/table:

i.e:

{id:1, name: 'personal'},
{id:2, name: 'business'},

and when the user select the option it will send just the id of the option not the entire object, so it will throw an error when i try to save the project.

Im not sure if im missing something or maybe i need to add a type_id property to the DTO, but im just joining the backend world, so i don't have too much experience, and even im not sure if this is approach is right. i just read the documentation from NestJS and typeORM

2

There are 2 best solutions below

0
Lazaro Falcon On

This is probably because you are doing something like this in your service

const project = await this.projectRpo.create(createProjectDto);

and projectDto doesn't match your project entity definition as a project has a many to one relation with type. You have to keep your dto definition as you have

  @ApiProperty({ description: 'project name', example: 'ToDo' })
  @IsNotEmpty()
  @IsString()
  readonly name: string;

  @ApiProperty({ description: 'type of project', example: 'Personal' })
  @IsOptional()
  readonly type: string;

But then in your service you will first have to search the Type entity that math the type you received in dto an then assign to the new Project instance your are creating something like this

const projectType = await this.projectTypeRepo.findOneBy(id: createProjectDto.type)
const newProject = this.projectRepo.create({...createProjectDto, type: projectType})
await this.projectRepo.save(newProject)

You should learn more about TypeOrm for better clarification. Hope it helps Lazaro

0
Cesar Gomez On

I just fixed by doing a couple changes:

DTO: I changed the field from type to type_id and changed from string to number

import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator';
export class CreateProjectDto {

  @ApiProperty({ description: 'project name', example: 'ToDo' })
  @IsNotEmpty()
  @IsString()
  readonly name: string;

  @ApiProperty({ description: 'type of project', example: 'Personal' })
  @IsOptional()
  readonly type_id: number;

}

then I modify my project entity by adding a column type_id and then creating the relationship ManyToOne and using the decorator JoinColumn

import {
  Column,
  Entity,
  JoinColumn,
  ManyToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { ProjectType } from '../project-type/entities/project-type.entity';

@Entity('projects')
export class Project {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column({ nullable: true })
  type_id: number;

  @ManyToOne(() => ProjectType)
  @JoinColumn({ name: 'type_id' })
  type: ProjectType;
}

then in the reverse part (project-type) I just modify the relation from ManyToOne to OneToMany:

import { Project } from '@/projects/entities/project.entity';
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';

@Entity('project_type')
export class ProjectType {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(() => Project, (project) => project.type)
  projects: Project[];
}

finally in the Project module I just added the projectType to the typeOrmModule.forFeature([])