Search code examples
javascripttypescriptpostgresqlnestjs

Why do I have to include another key to save db entities linked to another entity?


I am building a NestJS RESTful API for a library. I have a Book entity and a Category entity. Each book has a category.

Book entity

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

  @Column()
  title: string;

  @Column()
  author: string;

  @ManyToOne(() => Category, (category) => category.id)
  category: Category;

  @ManyToOne(() => User)
  @JoinColumn({ name: 'createdBy', referencedColumnName: 'id' })
  user: User;

  @Column()
  createdBy: number;
}

Category entity

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

    @Column()
    name: string;

    @Column()
    description: string;

    @OneToMany(() => Book, (book) => book.category)
    books: Book[];
}

This is the DTO to create a book:

export class CreateBookDto {
    @IsString()
    @MinLength(3)
    title: string;
    
    @IsString()
    @MinLength(3)
    author: string;

    @IsString()
    category: string;
}

This is the function that creates a new book:

async create(createBookDto: CreateBookDto, user: ActiveUserInterface) {
    const category = await this.validateCategory(createBookDto.category);
    return await this.bookRepository.save({
      ...createBookDto,
      category: category,
      createdBy: user.sub,
    });
  }

And this is the function that validates categories:

private async validateCategory(category: string) {
    const categoryEntity = await this.categoryRepository.findOneBy({
      name: category,
    });
    if (!categoryEntity) {
      throw new NotFoundException('Category not found');
    }
    return categoryEntity;
  }

My question is: why do I need to add again the category on the function to save the book on the DB if it's already defined on the DTO? Actually it works as I expect, but why don't they overlap each other at the time it's saved on the DB?

I tried to create a new book, and it works as expected. I just don't understand why does it work.


Solution

  • Because the category that's defined on your CreateBookDto is a string value, when you run your validateCategory function it returns a Category object which is used to create the relationship between Book and Category. If you pass a string instead of the category object it won't work. We need to use the Category object because it contains the category id which is need to create the relationship between them.

    You are not overlapping the values, you are setting a new value to the category property in the object that will be saved. You could say you are overwriting the previous category value with a new one.

    If you want to avoid setting the category again you should set the dto this way and pass a Category object instead of a string:

    export class CreateBookDto {
        @IsString()
        @MinLength(3)
        title: string;
        
        @IsString()
        @MinLength(3)
        author: string;
    
        // You need a category dto object with it's own decorators
        category: CategoryDto;
    }