node.jstypescriptnestjsclass-transformer

NestJS Mapping DTO to Entity with Relations


I am someone new to NestJS and was wondering what the best practice for converting a DTO to an entity is, when the DTO's attributes don't match up 1:1 with the entities attributes.

For example, i have the following entity definitions:

@Entity()
export class Category {

    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string
    
    @OneToMany(() => questionToCategory, questionToCategory => questionToCategory.category)
    public questionToCategories: QuestionToCategory[];
}
export class Question {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column()
    text: string

    @OneToMany(() => QuestionToCategory, questionToCategory => questionToCategory.question)
    public questionToCategories: QuestionToCategory[];
}
export class QuestionToCategory {
    @PrimaryGeneratedColumn()
    public questionToCategoryId: number

    @Column()
    public questionId: number

    @Column()
    public categoryId: number

    @Column()
    public order: number

    @ManyToOne(() => Question, (question) => question.questionToCategories)
    public question: Question

    @ManyToOne(() => Category, (category) => category.questionToCategories)
    public category: Category
}

with these entity definitions, there is a many to many relationship between question and categories

to save the many to many relation from the question entity, you could do something like this:

questionToCategories: { categoryId: "some_id_value" }

this works fine, however my DTO structure doesn't match this structure perfectly.

if my question dto is something like the following:

export class CreateQuestionDto {
    @IsString()
    title?: string;

    @IsString()
    title?: string;

  @IsOptional()
  @IsArray()
  bundles: string[];
}

where bundles is an array of id's corresponding to a category. I cant directly save this DTO as the entity, as the bundles field doesn't match up with questionToCategories. In NestJS is there any best practice to convert the dto to the equivalent representation of the entity


Solution

  • I am assuming that your DTO cant be changed and matched as per the entities. In that case you can not directly save via casting and this cant perform a cascade insert. What you can do is: Create a mapper service which will initially save the questions, get its ids, and than map it as per your QuestionToCategory Entity and than save that:

    import { Injectable } from '@nestjs/common';
    import { InjectRepository } from '@nestjs/typeorm';
    import { Repository } from 'typeorm';
    import { CreateQuestionDto } from './dto/create-question.dto';
    import { Question } from './entities/question.entity';
    import { Category } from './entities/category.entity';
    import { QuestionToCategory } from './entities/question-to-category.entity';
    
    @Injectable()
    export class QuestionService {
      constructor(
        @InjectRepository(Question)
        private questionRepository: Repository<Question>,
        @InjectRepository(Category)
        private categoryRepository: Repository<Category>,
        @InjectRepository(QuestionToCategory)
        private questionToCategoryRepository: Repository<QuestionToCategory>,
      ) {}
    
      async create(createQuestionDto: CreateQuestionDto): Promise<Question> {
        const { title, text, bundles } = createQuestionDto;
    
        const question = new Question();
        question.title = title;
        question.text = text;
    
        const savedQuestion = await this.questionRepository.save(question);
        const questionToCategories: QuestionToCategory[] = [];
    
        for (const categoryId of bundles) {
          const category = await this.categoryRepository.findOne(categoryId);
          if (category) {
            const questionToCategory = new QuestionToCategory();
            questionToCategory.question = savedQuestion;
            questionToCategory.category = category;
            questionToCategories.push(questionToCategory);
          }
        }
    
        await this.questionToCategoryRepository.save(questionToCategories);
        return this.questionRepository.findOne(savedQuestion.id, { relations: ['questionToCategories'] });
      }
    }
    

    In the service class you can inject repositories of your entities and use them to get and save in your tables accordingly