ORM을 간단히 설명해보자면, Object-relation mapping, 객체-관계 맵핑인데 공식 문서에서는 객체와 관계형 데이터베이스의 데이터를 자동으로 맵핑(연결)해준다.
그러니까 js
나 ts
파일안에 Class
를 활용해 객체를 만들어주고, 그 여러개의 객체를 관계를 맺어준 뒤에 DataBase
에 각종 테이블로 담아주기 위한 개념이다. ORM을 통해서 우리가 생성한 객체를 DB의 테이블로 생성해준다.
이전에 express
로 서버를 생성하고, 데이터베이스를 조합하기 위해선 흔히 알려져있는 ORM으로 Sequelize
라는 라이브러리가 있다. 보통의 경우 JavaScript
에서 쓸때 사용하고, TypeScript
에서는 typeORM
을 쓰는게 국룰이라고 봤었고, 지금까지도 그렇게 알고있다.
이것은 반박할 수가 없는것이 NestJS
공식 페이지에서도 TypeORM
에 대해 집중적으로 작성되어있고, 공식 문서를 읽고 찾기 어려운 Sequelize
보다 설명 및 정리가 잘되어있고, TypeScript
같이 데코레이터를 활용하여 어떤 모델을 쉽게 정의할 수 있기 때문에 TypeScript
환경에 맞는 라이브러리라고 생각한다.
그냥 쉽게말해서 TypeScript
는 TypeORM
일반 JavaScript
+express
에서는 Sequelize
정말 많다.. 나머지는 천천히 알아보자
이전 글에도 있지만 TypeORM에는 두가지 패턴으로 작성할 수 있고, 동시에 복합적으로 사용이 가능하다.
A. Active Record
Active Record 패턴은 모델 그 자체에
B. Data Mapper
우선 TypeORM모듈을 설치할 건데 @Nest/Config
도 같이 사용하면 좋으니..이건 추후에 자세히 다루겠다.
typeorm
모듈 설치
npm i typeorm reflect-metadata @types/node
DataBase
드라이버 설치
npm i mysql --save
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
를 보자. 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;
}
회원 가입할, 가입된 유저를 생각해볼 경우 여러가지가 있겠지만..
대충 이런 부분만 고려해서 작성해주었다.
이번엔 엔티티를 만들어보자
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을 연결지어보자.
라이브러리를 설치안했으면 추가로 설치해주면된다.
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;
}
@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()
는 @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
를 켜서 확인해보면 된다.
Mutation(
input:{
firstName: "kim",
lastName: "kim",
email:"kimkimkim@gmail.com"
}
){}
Mutation(
firstName: "kim",
lastName: "kim",
email: "kimkimkim@gmail.com"
)
@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
를 주면된다.