Search code examples
typescriptsqlitetypeorm

How to properly create a registry with ManyToMany relation?


I'm trying to save a Product that has many Categories. At the same time a Category can have many Products. So I think the best solution is a ManyToMany relationship to model my data.

When I try to create a new product, an error ocurred saying that it isn't possible insert a new Category with null values. The crazy thing is that I don't need to create new categories, instead that I want to take the existing ones and assign them to the new product.

This is the SQL message that console returns:

SQLITE_CONSTRAINT: NOT NULL constraint failed: category.name',
errno: 19,
code: 'SQLITE_CONSTRAINT',
name: 'QueryFailedError',
query: 'INSERT INTO "category"("id", "parent_id", "name", "description", "createdAt", "updatedAt") VALUES (NULL, NULL, NULL, NULL, datetime(\'now\'), datetime(\'now\'))

Following the typeorm documentation example I deduce that my code must be something like next:

Product.ts

@Entity()
@Unique(["code"])
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({
    length: 20
  })
  code: string;

  @Column({
    length: 100
  })
  name: string;

  @Column()
  description: string;

  @Column()
  stock: number;

  @Column({
    type: "real"
  })
  price: number;

  @Column()
  public_price: boolean;

  @Column()
  public: boolean;

  @ManyToMany(type => Category, category => category.products, {
    cascade: true
  })
  @JoinTable()
  categories: Category[];
}

Category.ts

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

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

  @Column()
  @IsNotEmpty()
  name: string;

  @Column()
  description?: string;

  @Column()
  @CreateDateColumn()
  createdAt: Date;

  @Column()
  @UpdateDateColumn()
  updatedAt: Date;

  @ManyToMany(type => Product, product => product.categories)
  products: Product[];
}

ProductController.ts (create method)

static create = async (req: Request, res: Response) => {
  const { categories, ...data } = req.body

  const categoryRepository = getRepository(Category);
  const productRepository = getRepository(Product);

  const cats = []
  for (const id of categories) {
    const cat = await categoryRepository.find(id)
    cats.push(cat)
  }
    
  const product = new Product();
  for (const index in data) {
    product[index] = data[index]
  }
  product.categories = cats

  try {
    await productRepository.save(product);
    res.send('Product successfuly created');
  } catch (err) {
    console.log(err)
    res.status(409).send('Error on product creation');
  }
}

And this is the JSON object that I'm using to test my application through Postman:

{
    "code": "1234",
    "name": "TESTING PRODUCT CREATION",
    "description": "Product sended through postman",
    "stock": "99",
    "price": "255.88",
    "public_price": true,
    "public": true,
    "categories": [1]
}

Solution

  • Replace

    await categoryRepository.find(id)
    

    with

    await categoryRepository.findOne(id)
    

    find will return the item in Array.