본문 바로가기

NestJs - Join API 구현 본문

개발/nest.js

NestJs - Join API 구현

자전하는명왕성 2023. 2. 9. 00:50

상당히 어려웠던 부분이다.

내가 구현했던 ERD에는

일 대 일 관계와 일 대 다 관계는 없기에, 

다 대 일 / 다 대 다 관계에서 다룬다.

 

다 대 일

다 대 일로 작업한 내용은 카테고리였다.

// dto create-product.input.ts 
  @Field(() => String)
  productCategory: string; // dto 에 카테고리 추가
  
  // product.service.ts
  create 함수 내부에 추가
  
  create({
    const { productCategory, ...product } = createProductInput; 
    // 구조 분해 할당
      
  }
   const result = this.productsRepository.save({
      ...product,
      productCategory: { category_id: productCategory }, 
      // 이떄, category_id == '카테고리 엔티티'안에 있는 카테고리 아이디 변수명이다.
    });  
  return result
  }

// 조회할 때 함께 조회할 수 있도록 relations 에 추가
// findAll & findOne 은 read 를 위한 함수와 연결된 함수
  // findAll
  findAll(): Promise<Product[]> {
    return this.productsRepository.find({
      relations: ['productCategory'],	// 엔티티 이름을 문자열 타입으로 넣어준다.
    });
  }

  // findOne
  findOne({ productId }: IProductServiceFindOne): Promise<Product> {
    return this.productsRepository.findOne({
      where: { id: productId },
      relations: ['productCategory'],
    });
  }

플레이그라운드에서 확인한 모습

 

다 대 다 

다 대 다의 경우는 brand / allergy 를 적용하는 과정이었는데,

방식 자체는 동일하므로 하나에 대해서만 다룬다.

이 경우에는 input 할 때 함께 입력하는 방식을 택한다.

 

마찬가지로 dto에 파일을 수정한다.

// dto create-product.input.ts 
  @Field(() => [String])	// graphql type 
  productBrands: string[]; 	// typescript type

  @Field(() => [String])
  productAllegies: string[];
  
  // product.service.ts
  서비스 주입
 constructor(
    @InjectRepository(Product)
    private readonly productsRepository: Repository<Product>,
    private readonly productsAllegiesService: ProductsAllegiesService, // 알러지
    private readonly productsBrandsService: ProductBrandService, // 브랜드
  ) {}
  
  
  // create 함수 내부에 추가
  
  create({
    const { productCategory, productBrand, productAllegies...product } = createProductInput; 
    // 구조 분해 할당
      
  }
  
    // 중복된 브랜드값을 걸러내어 저장하기 위한 로직
  const prevBrands = await this.productsBrandsService.findByBrands({
      productBrands,	// 기존 저장되어 있는 브랜드 배열 
      		// findByBrands 는 Read 와 함께쓰는 함수 / 밑에서 설명
    });

    const tempForBrand = [];
    productBrands.forEach((el) => {
      const isExists = prevBrands.find((prevEl) => el === prevEl.brand_name);
      if (!isExists) tempForBrand.push({ brand_name: el });
    });	// 기존 저장된 배열과 일치하지 않을 경우 추가하는 알고리즘

    const newBrands = await this.productsBrandsService.bulkInsert({
      names: tempForBrand,	// bulkInsert 밑에서 설명
    });
    const brandsNames = [...prevBrands, ...newBrands.identifiers];
    
    //
   const result = this.productsRepository.save({
      ...product,
      productCategory: { category_id: productCategory },
      productAllegies: allegiesNames,	// 결과로 전송
      productBrands: brandsNames,	// product.entity : input
    });
  return result
  }

// 조회할 때 함께 조회할 수 있도록 relations 에 추가
// findAll & findOne 은 read 를 위한 함수와 연결된 함수
  // findAll
  findAll(): Promise<Product[]> {
    return this.productsRepository.find({
      relations:  ['productCategory', 'productAllegy', 'productBrand'],	// 엔티티 이름을 문자열 타입으로 넣어준다.
    });
  }

  // findOne
  findOne({ productId }: IProductServiceFindOne): Promise<Product> {
    return this.productsRepository.findOne({
      where: { id: productId },
      relations:  ['productCategory', 'productAllegy', 'productBrand'],
    });
  }
  
  
  // productBrand.service.ts
  
@Injectable()	// DI
export class ProductBrandService {
  constructor(
    @InjectRepository(ProductBrand)
    private readonly productBrandRepository: Repository<ProductBrand>,
  ) {}
  findByBrands({ productBrands }: IProductBrandServiceFindBynames) {
    return this.productBrandRepository.find({
      where: { brand_name: In(productBrands) }, // brand 를 찾는 함수
    });
  }
  bulkInsert({ names }: IProductBrandsServiceBulkInsert) {
    return this.productBrandRepository.insert(names); // 대량으로 저장하는 함수 // 다량의 데이터에 효과적
  }
}

 

Comments