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 관계를 설정하기 위해서 만들기 위해 다음과 같이 연결해 두었습니다.
따라서 product
와 product_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;
}
}
로직을 순서대로 설명 드리겠습니다.
createProductInput
을 스프레드 연산자를 사용했습니다.productSaleslocation
을 데이터에 저장했고 result1
에 데이터를 할당했습니다.productCategory
에 데이터베이스에 요청된 데이터를 조회합니다.result3
빈 배열을 생성하고 태그 앞에 존재하는 '#'을 제거하고 데이터베이스 해당하는 태그를 조회해서 이미 존재하면 result3
에 추가하고 존재하지 않으면 데이터를 저장하고 result3
에 추가합니다.productTag
포함시켜서 product
를 등록합니다.서버를 다시 실행시켜주세요.
http://localhost:3000/graphql 에 접속해서 플레이그라운드에서 api를 요청해보세요.
먼저 상품 카테고리 테이블에 데이터를 생성하기 위해서 createProductCategory
에 요청을 보냈습니다.
생성된 상품 카테고리 ID를 복사해주세요.
다음과 같이 createProduct
에 요청을 보내 상품을 등록했습니다.
DBeaver를 실행해서 상품이 잘 등록이 되었는지 확인해보겠습니다.
상품의 태그 테이블에 데이터가 잘 등록되어있네요.
상품과 상품 태그의 중간 테이블에 데이터가 잘 생성되었습니다.
19-01-product-curd-create-many-to-many
폴더를 복사하여 사본을 만들고 19-02-product-curd-read-many-to-many copy
로 변경해주세요.
이번에는 product
테이블을 조회하면서 연결되어 있는 다른 데이블의 데이터도 조회해보겠습니다.
다음과 같이 product.service.ts
에 findAll(), 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를 만들어보겠습니다.
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 요청을 보내주세요.
정상적으로 회원가입이 되었습니다.