[NestJS] TypeORM과 GraphQL 데코레이터

캡틴 노드랭크·2021년 10월 11일
0

NestJS

목록 보기
5/5

ORM의 개요

ORM을 간단히 설명해보자면, Object-relation mapping, 객체-관계 맵핑인데 공식 문서에서는 객체와 관계형 데이터베이스의 데이터를 자동으로 맵핑(연결)해준다.

그러니까 jsts파일안에 Class를 활용해 객체를 만들어주고, 그 여러개의 객체를 관계를 맺어준 뒤에 DataBase에 각종 테이블로 담아주기 위한 개념이다. ORM을 통해서 우리가 생성한 객체를 DB의 테이블로 생성해준다.

그런데 왜 TypeORM을 쓰는거야?

이전에 express로 서버를 생성하고, 데이터베이스를 조합하기 위해선 흔히 알려져있는 ORM으로 Sequelize라는 라이브러리가 있다. 보통의 경우 JavaScript에서 쓸때 사용하고, TypeScript에서는 typeORM을 쓰는게 국룰이라고 봤었고, 지금까지도 그렇게 알고있다.

이것은 반박할 수가 없는것이 NestJS 공식 페이지에서도 TypeORM에 대해 집중적으로 작성되어있고, 공식 문서를 읽고 찾기 어려운 Sequelize보다 설명 및 정리가 잘되어있고, TypeScript같이 데코레이터를 활용하여 어떤 모델을 쉽게 정의할 수 있기 때문에 TypeScript환경에 맞는 라이브러리라고 생각한다.

그냥 쉽게말해서 TypeScriptTypeORM
일반 JavaScript+express에서는 Sequelize

TypeORM이 자랑하는 무시무시한 기능들.

정말 많다.. 나머지는 천천히 알아보자

TypeORM의 두가지 패턴

이전 글에도 있지만 TypeORM에는 두가지 패턴으로 작성할 수 있고, 동시에 복합적으로 사용이 가능하다.

A. Active Record

Active Record 패턴은 모델 그 자체에

B. Data Mapper

TypeORM 시작하기

우선 TypeORM모듈을 설치할 건데 @Nest/Config도 같이 사용하면 좋으니..이건 추후에 자세히 다루겠다.

typeorm 모듈 설치

npm i typeorm reflect-metadata @types/node

DataBase 드라이버 설치

  • Mysql or MariaDB

npm i mysql --save

  • PostgreSQL or CockroachDB

npm i pg --save

  • MSSQL(SQL Server)
    npm i mssql --save

  • Oracle
    npm i oracledb --save

  • MongoDB

npm install mongodb@^3.6.0 --save

각 취향에 맞는 DB를 선택해 설치해주면된다.

App.Module

우선 엔티티와 모듈을 만들기 전에 최상단 루트의 app.module를 보자. TypeOrmModule를 가져와 엔티티를 구성하고 사용하기 위해서는 각 디렉토리에서 가져와야한다.

  TypeOrmModule.forRoot({
      type: 'mysql',
      host: process.env.MYSQL_HOST,
      port: +process.env.MYSQL_PORT,
      username: process.env.MYSQL_USER,
      password: process.env.MYSQL_PASS,
      database: process.env.MYSQL_DB,
      synchronize: true,
      logging: false,
      entities: [USER_TB],
    }),

port부분을 보면 +를 붙였는데, 단순히 환경변수로 불러올 경우 String으로 인식하기 때문에 에러가 발생한다. 앞에 +를 붙여서 타입을 변환하자.

모델과 엔티티 생성

우선 유저의 모델을 간단하게 작업해보자

export class USER_TB  {
  id: number;
  created_at: Date;
  updated_at: Date;
  uuid: string;
  user_email: string;
  user_pwd: string;
  user_nick: string;
  user_role: string;
  is_login: boolean;
  deleted_at: Date;
}

회원 가입할, 가입된 유저를 생각해볼 경우 여러가지가 있겠지만..

  • 고유 값을 가진 필드가 존재
  • 유저 가입일, 프로필 수정일 존재
  • 복합키 혹은 추가적인 조회에 이용할 수 있는 고유한 uuid
  • 입력, 수정할 이메일, 닉네임
  • 유저의 권한(Role)
  • 로그인 여부
  • 회원 탈퇴일..

대충 이런 부분만 고려해서 작성해주었다.

이번엔 엔티티를 만들어보자

엔티티와 유효성 검사

import { Column, DeleteDateColumn, Entity, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';

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

  @Column()
  @IsDate()
  created_at: Date;
  
  @Column()
  @IsDate()
  updated_at: Date;
  
  //단순히 필드로 활용할 경우
  @Column()  //만약 복합키로 이용하고 싶으면??
  uuid: string;

     OR
  // 복합키로 지정할 경우
  @PrimaryColumn()
  @IsString()
  uuid: string;

  @Column()
  @IsString()
  user_email: string;

  @Column()
  @IsString()
  user_pwd: string;

  @Column()
  @IsString()
  user_nick: string;

  @Column()
  @IsString()
  user_role: string;

  @Column()
  @IsBoolean()
  is_login: boolean;

  @Column()
  @IsDate()
  deleted_at: Date;
}

GraphQL과 TypeORM 엔티티

이번엔 GraphQL과 TypeORM을 연결지어보자.

라이브러리를 설치안했으면 추가로 설치해주면된다.

npm i @nestjs/graphql

@Mutation,@Query,@Args,@ArgsType,@InputType,@ObjectType 같은 데코레이터를 사용할 수 있다. 자동으로 스키마를 생성하고, 전달 등등을 활용할 수 있는 유용한 기능이다.

import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { IsBoolean, IsDate, IsEnum, IsString } from 'class-validator';

import {
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  PrimaryColumn,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

export enum UserRole {
  ADMIN = 'ADMIN',
  USER = 'USER',
  GHOST = 'GHOST',
}

@InputType({ isAbstrict: true })
@ObjectType()
@Entity()
export class USER_TB  {
  @PrimaryGeneratedColumn('uuid')
  @Field((type) => String)
  @IsInt()
  id: string;

  @Column()
  @Field((type) => Date)
  @IsDate()
  created_at: Date;
  
  @Column()
  @Field((type) => Date)
  @IsDate()
  updated_at: Date;

  @Column()
  @Field((type) => String)
  @IsString()
  user_email: string;

  @Column()
  @Field((type) => String)
  @IsString()
  user_pwd: string;

  @Column()
  @Field((type) => String)
  @IsString()
  user_nick: string;

 @Column({
    name: 'ROLE',
    type: 'enum',
    enum: UserRole,
    default: UserRole.USER,
  })
  @Field(() => UserRole)
  @IsEnum(UserRole)
  user_role: UserRole;

  @Column()
  @Field((type) => Boolean)
  @IsBoolean()
  is_login: boolean;

  @Column()
  @Field((type) => Date)
  @IsDate()
  deleted_at: Date;
}

@ObjectType()

  • @ObjectType()는 스키마를 자동으로 생성하기 위해 사용하는 GraphQL 데코레이터이다.

@ArgsType()

  • @ArgsType()는 @Args()를 한번 호출의 하나의 인자를 받는다. 그렇게 여러개의 입력을 받아야 하는 경우 코드 양이 늘어난다. 이때는 @ArgsType()를 활용해 여러개의 인자로 받을 수 있다.

Resolver.ts

export class TestResolver {
@Mutation((returns) => Post)
async getAuth(
  @Args('firstName') firstName?: string,
  @Args('lastName') lastName?: string,
  @Args('email') email?: string,
  ){}
}

이거를 획기적으로 줄일 수 있는게 장점이다.

export class TestResolver {
@Mutation((returns) => Post)
async getAuth(@Args() args: GetAuthArgs){}
}

GetAuthArgs.args.ts

import { MinLength } from 'class-validator';
import { Field, ArgsType } from '@nestjs/graphql';

@ArgsType()
class GetAuthArgs {
  @Field({ nullable: true })
  firstName?: string;

  @Field({ defaultValue: '' })
  @MinLength(3)
  lastName: string;

  @Field({ nullable: true })
  email?: string
}

@InputType()

@InputType()@ArgsType()와는 다르게 하나의 인자를 받는다.

export class TestResolver {
@Mutation((returns) => Post)
async getAuth(@Args('input') args: GetAuthArgs){
     return .....
  }
}

여기서 @Args('string_name') 같이 String타입의 인자를 필수로 넣어야한다. 필수로

@InputType()
class GetAuthArgs {
  @Field({ nullable: true })
  firstName?: string;

  @Field({ defaultValue: '' })
  @MinLength(3)
  lastName: string;

  @Field({ nullable: true })
  email?: string
}

GraphQL Playground를 켜서 확인해보면 된다.

InputType와 ArgsType의 GraphQL

  • InputType()
Mutation( 
  input:{
    firstName: "kim",
    lastName: "kim",
    email:"kimkimkim@gmail.com"
  }
 ){}
  • ArgsType()
Mutation(
  firstName: "kim",
  lastName: "kim",
  email: "kimkimkim@gmail.com"
)

@Field

@Field()도 GraphQL 데코레이터로, 데이터베이스의 각각의 필드를 형성해주는 역할이므로 클래스 내부에 작성해야한다. 이 필드에는 3가지 옵션과 스칼라 유형을 작성해줄 수 있다.

  • Field의 옵션
@Field({ 
          nullable: true, //Null값을 허용 여부, Boolean
          description: `aabbcc` // 필드를 설명하는 내용, String
          deprecationReason: `AABBDD` //더이상 사용되지 않는 필드 표시, String
       })
  • Field가 배열을 받으며, Null일수도 있을 경우
@Field(() => Post[], { nullable: 'items' })
posts: Post[];

nullable:items를 설정해놓으면, 배열의 항목이(배열 자체가 아님) null일수 있다.
해당 배열 자체와 항목 모두가 Null일 경우 itemsAndList를 주면된다.

profile
다시 처음부터 천천히... 급할필요가 없다.

0개의 댓글