N:M 등록/조회 & 회원가입

류연찬·2023년 5월 22일
0

GraphQL

목록 보기
15/17

N:M 등록

18-05-product-crud-delete-cascade 폴더를 복사해서 사본을 만든 후 폴더명을 19-01-product-crud-create-many-to-many 로 변경해주세요.

yarn start:dev 를 통해 서버를 실행시켜주세요.

서버를 실행시키고 DBeaver를 실행해서 생성된 테이블을 한번 확인해보겠습니다.

product_product_tags_product_tag 테이블이 생성된걸 확인할 수 있습니다.

엔티티를 작성하지 않았는데 테이블이 생성되었습니다.

N:M 관계를 설정하기 위해서 만들기 위해 다음과 같이 연결해 두었습니다.

따라서 productproduct_tag 의 N:M 관계를 설정해 놓았기 때문에 NestJS는 자동으로 1:N & 1:M의 중간 테이블을 생성해 주었습니다.

엔티티의 관계도는 다음과 같습니다.



이제 product 테이블에 데이터를 생성하면서 다른 테이블도 연결하여 등록해보겠습니다.

다음과 같이 createProduct.input.ts 를 수정해주세요.

import { Field, InputType, Int } from '@nestjs/graphql';
import { ProductSaleslocationInput } from 'src/apis/productSaleslocation/dto/productSaleslocation.input';

@InputType()
export class CreateProductInput {
  @Field(() => String)
  name: string;

  @Field(() => String)
  description: string;

  @Field(() => Int)
  price: number;

  @Field(() => ProductSaleslocationInput)
  productSaleslocation: ProductSaleslocationInput;

  @Field(() => String)
  productCategoryId: string;

  @Field(() => [String])
  productTags: string[];
}

product 테이블이 생성되면서 연결되어 있는 테이블도 생성이 가능합니다.

product.service.ts 파일의 create 를 다음과 같이 수정해주세요.

@Injectable()
export class ProductService {
  constructor(
  	// ...기존 코드
  
  	@InjectRepository(ProductTag)
    private readonly productTagRepository: Repository<ProductTag>
  )
  
  // ... 기존 코드 
  
  async create({ createProductInput }: ICreate) {
    const { productSaleslocation, productCategoryId, productTags, ...product } =
      createProductInput;

    const result1 = await this.productSaleslocationRepository.save({
      ...productSaleslocation,
    });

    const result2 = await this.productCategoryRepository.findOne({
      where: { id: productCategoryId },
    });

    // 이미 존재하는 태그이면 저장을 하지 않고 이미 존재하지 않으면 태그 데이터 저장
    const result3 = [];
    if (productTags.length) {
      // 추후 for문을 forEach와 Promise.all로 최적화 예정
      for (let i = 0; i < productTags.length; i++) {
        const tagName = productTags[i].replace('#', '');
        const prevTag = await this.productTagRepository.findOne({
          where: { name: tagName },
          relations: ['products'],
        });

        if (prevTag) {
          result3.push(prevTag);
        } else {
          const newTag = await this.productTagRepository.save({
            name: tagName,
          });

          result3.push(newTag);
        }
      }
    }

    const result4 = await this.productRepository.save({
      ...product,
      productSaleslocation: result1,
      productCategory: result2,
      productTags: result3,
    });

    return result4;
  }
}

로직을 순서대로 설명 드리겠습니다.

  1. createProductInput 을 스프레드 연산자를 사용했습니다.
  2. productSaleslocation 을 데이터에 저장했고 result1 에 데이터를 할당했습니다.
  3. productCategory 에 데이터베이스에 요청된 데이터를 조회합니다.
  4. result3 빈 배열을 생성하고 태그 앞에 존재하는 '#'을 제거하고 데이터베이스 해당하는 태그를 조회해서 이미 존재하면 result3 에 추가하고 존재하지 않으면 데이터를 저장하고 result3 에 추가합니다.
  5. 방금 등록/조회한 productTag 포함시켜서 product 를 등록합니다.

서버를 다시 실행시켜주세요.

http://localhost:3000/graphql 에 접속해서 플레이그라운드에서 api를 요청해보세요.

먼저 상품 카테고리 테이블에 데이터를 생성하기 위해서 createProductCategory 에 요청을 보냈습니다.

생성된 상품 카테고리 ID를 복사해주세요.

다음과 같이 createProduct 에 요청을 보내 상품을 등록했습니다.

DBeaver를 실행해서 상품이 잘 등록이 되었는지 확인해보겠습니다.

상품의 태그 테이블에 데이터가 잘 등록되어있네요.

상품과 상품 태그의 중간 테이블에 데이터가 잘 생성되었습니다.

N:M 조회

19-01-product-curd-create-many-to-many 폴더를 복사하여 사본을 만들고 19-02-product-curd-read-many-to-many copy 로 변경해주세요.

이번에는 product 테이블을 조회하면서 연결되어 있는 다른 데이블의 데이터도 조회해보겠습니다.

다음과 같이 product.service.tsfindAll(), find() 를 수정해주세요.

// product.service.ts

export class ProductService {
  // ... 기존 코드

  async findAll() {
    const products = await this.productRepository.find({
      relations: ['productSaleslocation', 'productCategory', 'productTags'],
    });
    return products;
  }

  async findOne({ productId }: { productId: string }) {
    const product = await this.productRepository.findOne({
      where: { id: productId },
      relations: ['productSaleslocation', 'productCategory', 'productTags'],
    });
    return product;
  }
  
  // ... 기존 코드
}

findAll() 은 상품 목록의 모든 데이터를 조회하며, relations 옵션에 ['productSaleslocation', 'productCategory', 'productTags'] 배열 안에 넣어서 설정해주면 관련되어 있는 테이블의 데이터까지 조회가 가능합니다.

find() 는 productId에 해당하는 상품만 조회하며, where 옵션에 { id: productId } 로 조건을 달아줘서 해당하는 상품을 찾고 relations 옵션에 ['productSaleslocation', 'productCategory', 'productTags'] 배열 안에 넣어서 설정해주면 조건에 해당하는 상품과 관련되어 있는 테이블의 데이터까지 모두 조회가 가능합니다.

yarn start:dev 로 서버를 실행시켜주세요.

http://localhost:3000/graphql 에 접속해서 플레이그라운드에서 api 요청해보세요.



회원가입 API

이제는 로그인을 하기전에 회원가입을 하는 API를 만들어보겠습니다.

19-02-product-crud-read-many-to-many 폴더를 복사하여 사본을 만들고 19-03-signup 으로 변경해주세요.

User Entity를 다음과 같이 수정해주세요.

password는 외부에 노출되면 안되기 때문에 다음과 같이 주석처리했습니다.

// user.entity.ts

import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
@ObjectType()
export class User {
  @PrimaryGeneratedColumn('uuid')
  @Field(() => String)
  id: string;

  @Column()
  @Field(() => String)
  email: string;

  @Column()
  // @Field(() => String) 비밀번호 노출 금지
  password: string;

  @Column()
  @Field(() => Int)
  age: number;
}

src/apis/user 폴더에 user.service.ts 파일을 만들어주세요.

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';

interface ICreate {
  email: string;
  password: string;
  name: string;
  age: number;
}

@Injectable()
export class UserService {
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
  ) {}

  async create({ email, password, name, age }: ICreate) {
    return await this.userRepository.save({ email, password, name, age });
  }
}

DB에 유저 정보를 정하는 비즈니스 로직은 위와 같습니다.

src/apis/user 폴더에 user.resolver.ts 파일을 생성해주세요.

import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { UserService } from './user.service';
import { User } from './entities/user.entity';

@Resolver()
export class UserResolver {
  constructor(private readonly userService: UserService) {}

  @Mutation(() => User)
  async createUser(
    @Args('email') email: string,
    @Args('password') password: string,
    @Args('name') name: string,
    @Args('age') age: number,
  ) {
    return this.userService.create({ email, password, name, age });
  }
}

src/apis/user 폴더에 user.module.ts 파일을 생성해주세요.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UserResolver } from './user.resolver';
import { UserService } from './user.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserResolver, UserService],
})
export class UserModule {}

그리고 app.module.ts 에 모듈을 추가합니다.

import { Module } from '@nestjs/common';
import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo';
import { GraphQLModule } from '@nestjs/graphql';
import { BoardModule } from './apis/board/board.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductCategoryModule } from './apis/productCategory/productCategory.module';
import { ProductModule } from './apis/product/product.module';
import { UserModule } from './apis/user/user.module';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: 'src/commons/graphql/schema.gql',
    }),
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'duscks0826@',
      database: 'myproject',
      entities: [__dirname + '/apis/**/*.entity.*'],
      synchronize: false,
      logging: true,
    }),
    BoardModule,
    ProductCategoryModule,
    ProductModule,
    UserModule, // 추가
  ],
})
export class AppModule {}

yarn start:dev 로 서버를 실행시킵니다.

그리고 http://localhost:3000/graphql 에 접속해서 플레이그라운드에서 API 요청을 보내주세요.

정상적으로 회원가입이 되었습니다.

0개의 댓글