데이터베이스#
ORM#
TypeORM#
- SQLite
- Postgres
- MySQL
- MongoDB
Mongoose#
아무거나 사용해도 된다.
여기서는 TypeORM과 SQLite를 사용할 것. 마지막에는 Postegres로 변경할 계획
Entity#
우리는 AppModule 내부에[ 두가지 모듈(users, reports)을 만들기로 계획했었다. 이 모듈에는 각각 User Entity와 ReportEntity 파일을 만들 것이다.
Entity 파일은 애플리케이션 내부에 저장하려는 한 종류의 리소스나 항목을 정의하고, 갖고 있는 모든 속성을 나열하는 리스트이다. 예를 들어 User 모듈에는 사용자가 입력한 email과 password가 있어야 하는데, User Entitiy에 나와 있어야 한다.
Repository#
TypeORM은 각 모듈에 Repository를 자동으로 생성해준다. 저번에 수동으로 만들지 않은 이유가 이 때문이다.
DB 연결하기#
터미널에서 현재 프로젝트의 디렉토리(mycv)로 가서
npm install @nestjs/typeorm typeorm sqlite3를 입력하여 orm과 DB를 설치한다.
src의 app.module.ts 파일을 수정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; // 추가 코드
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { ReportsModule } from './reports/reports.module';
@Module({
imports: [
TypeOrmModule.forRoot({ // 추가된 코드
type: 'sqlite', // DB 유형
database: 'db.sqlite', // DB 이름
entities: [], // 나중에 user entity와 report entity를 넣어줄 것
synchronize: true, // 나중에 알게 될 것
}),
UsersModule,
ReportsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
|
(이 과정에서 뜨는 오류가 거슬리면 .eslintrc.js 파일에서 module.exports 의 내부를 모두 주석처리하자.)
이제 터미널에서 npm run start:dev를 입력하여 실행해보자.
에러없이 실행되면 루트 디렉토리에 db.sqlite라는 파일이 비어있는 채로 새롭게 생성된다. SQLite는 파일 기반 데이터베이스이기 떄문이다.
Entity 생성하기#
3단계로 구성된다.
- Entity 파일을 만들고 entity가 가질 모든 속성을 나열하는 클래스를 생성한다.
- entity를 부모 모듈과 연결한다. 여기서 Repository가 생성된다.
- entity를 app module의 root connection과 연결한다.
1단계#
src 내 user 디렉토리에 user.entity.ts 파일을 만든다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; // decorator들
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
@Column()
password: string;
}
|
2단계#
user 모듈로 가서 연결한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
| // user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; // 추가된 코드 1
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './user.entity'; // 추가된 코드 2
@Module({
imports: [TypeOrmModule.forFeature([User])], // 추가된 코드 3
controllers: [UsersController],
providers: [UsersService]
})
export class UsersModule {}
|
3단계#
App.module.ts 로 가서
import { User } from ;./users/user.entity; 를 추가하고
비워놨던 entities 애 User를 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { ReportsModule } from './reports/reports.module';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'db.sqlite',
entities: [User],
synchronize: true,
}),
UsersModule,
ReportsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
|
서버를 실행했을 때 오류가 발생하면 안된다.
위 과정을 Reports 모듈에도 적용하면서 연습해보자
- Entity 파일 만들기
1
2
3
4
5
6
7
8
9
10
11
| // reports.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Report {
@PrimaryGeneratedColumn()
id: number;
@Column()
price: number;
}
|
- Report module에 연결하기
1
2
3
4
5
6
7
8
9
10
11
12
13
| // reports.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ReportsController } from './reports.controller';
import { ReportsService } from './reports.service';
import { Report } from './reports.entity';
@Module({
imports: [TypeOrmModule.forFeature([Report])],
controllers: [ReportsController],
providers: [ReportsService],
})
export class ReportsModule {}
|
- App module에 연결하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { ReportsModule } from './reports/reports.module';
import { User } from './users/user.entity';
import { Report } from './reports/reports.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'db.sqlite',
entities: [User, Report],
synchronize: true,
}),
UsersModule,
ReportsModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
|
db.sqlite 파일 내용 보기#
여기까지 완료된 후 db.sqlite 파일을 열어보면 아까는 비어있는 파일이 열린 것과 달리 열리지 않는다.
확장 프로그램을 통해 무엇이 들어갔는지 확인해보자. sqlite 라는 확장프로그램을 설치하자.
설치 후 컨트롤 + 쉬프트 + P (맥북은 커맨드 + P)를 누르고 sqlite를 입력하여 SQLite: Open Databases를 선택하자
아무 변화가 없는것처럼 보일수 있지만 왼쪽 exploer를 보면 OUTLINE, TIMELINE 밑에 SQLITE EXPLORER이 새로 열렸다. 열어보자.
들어가보면 우리가 작성한대로 데이터베이스가 잘 만들어졌음을 볼 수 있다.

코드 이해하기#
app.moudle.ts 파일에 추가한 코드부터 살펴보자
1
2
3
4
5
6
7
8
| @Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite', // 우리가 선택한 데이터베이스
database: 'db.sqlite', // 데이터 저장할 파일 지정
entities: [User, Report], // 로드할 entity
synchronize: true,
}),
|
동기화(synchronization)의 이유#
SQL 데이터베이스는 행, 열이 정확하게 갖추어져있다. 여서 구조를 변경할때(열이나 행 추가 혹은 삭) Migration을 실행한다.
이 동기화는 개발환경에서만 실행해야 하는데, entity, decorator를 통해 모든 구조를 파악고 자동으로 데이터베이스 구조를 업데이트한다.
이런 일은 흔치 않아서 보통은 데이터베이스 구조를 변경할때 마이그레이션 파일을 작성해야 한다.
저장소에 대해 이해해보기#
Repository API#
api 링크
| method | description |
|---|
| create() | 새로운 entity 인스턴스를 생성. DB에 유지되진 않음 |
| save() | 기록을 DB에 추가하거나 업데이트 |
| find() | 쿼리를 실행하고 entity 목록을 반환 |
| findOne() | 쿼리를 실행하여 탐색값과 일치하는 첫번째 기록을 반환 |
| remove() | DB에서 기록을 제 |
외울 필요 없다. Document에는 미세한 차이(remove VS delete, insert vs save) 까지 나와있으니 참고하자.
Routes 만들기#
처음 API를 계획할 떄, User와 관련되어 새로운 유저의 회원가입과 로그인, 기존 유저의 로그인 2가지를 기획했다.
TypeORM을 연습하기 위해 이를 더 세분하게 쪼개서 비록 불필요해보이는 기능일지라도 만들어보자.
만들 것은 다음과 같다.
| Mothod and Route | Body or Query String | Description | Controller Method | Service Method |
|---|
| POST /auth/signup | Body - { email, password } | 새로운 유저 생성 | createUser | create |
| GET /auth/:id | - | id로 한 명의 유저 찾기 | findUser | findOne |
| GET /auth?email=… | - | email로 모든 유저 찾기 | findAllUsers | find |
| PATCH /auth/:id | Body - { email, password } | id로 유저 업데이트하기 | updateUser | update |
| DELETE /auth/:id | - | id로 유저 삭제하 | removeUser | remove |
Body validation#
가장 첫번쨰 route를 만들어보자.
우선 user controller 파일을 수정한다.
1
2
3
4
5
6
7
8
| // user.controller.ts
import { Controller, Post} from '@nestjs/common';
@Controller('auth') // route가 모두 /auth/이니 user에서 바꿔주자
export class UsersController {
@Post('/signup') // decorator
createUser() {}
}
|
유저 생성 요청이 들어오면 Body의 유효성을 검사해야한다. 이전에 공부한 DTO를 만들어보자(기억이 안나면 잠시 돌아가보자)
main.ts 파일로 이동한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common'; // 추가한 코드 1
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes( // 추가한 코드 2
new ValidationPipe({
whitelist: true // 이 코드의 의미는 이따 설명할 예정이다.
}),
);
await app.listen(3000);
}
bootstrap();
|
다시 src의 user 디렉토리로 돌아가 dtos 폴더를 만들고 내부에 create-user.dto.ts를 만든다.
1
2
3
4
5
6
7
8
9
10
| // create-user.dto.ts
import { IsEmail, IsString } from 'class-validator'; // 유효성 검사를 위한 decorator들이 많았다는 것을 기억하자.
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
password: string;
}
|
패키지가 설치되어있지 않아 위에서 오류가 발생한다면, 서버를 멈추고
npm install class-validator class-transformer
를 입력여 라이브러리를 설치하자.
만든 dto를 컨트롤러에 적용할 차례.
1
2
3
4
5
6
7
8
9
10
11
| // users.controller.ts
import { Controller, Post, Body } from '@nestjs/common'; // 들어오는 request의 body를 추출하기 위해 Body도 import 한다.
import { CreateUserDto } from './dtos/create-user.dto'; // dto import하기
@Controller('auth')
export class UsersController {
@Post('/signup')
createUser(@Body() body: CreateUserDto) { // body decorator로 추출하고 검사한다.
console.log(body);
}
}
|
확인해보기#
API Client를 이용하여 확인해보자. 이번에도 postman 안쓰고 Rest client를 쓰겠다.
user 디렉토리 안에 requests.http 파일을 만들고 다음과 같이 입력하고 요청을 보내보자
1
2
3
4
5
6
7
8
| ### Create a new user
POST http://localhost:3000/auth/signup
content-type: application/json
{
"email": "importunate-dev@naver.com",
"password": "19940411"
}
|

잘 만들어졌다. 콘솔창에도 받은 정보가 뜬다.
DTO를 통한 validation의 작동 확인을 위해 잘못된 이메일 형식을 보내보자

password를 빼고 보내보자.

굿
whitelist#
DTO 설정시 main.ts파일에 whitelist: true라고 표시했었다.
이 코드는 들어오는 요청의 body에 예상 외의 속성이 들어오더라도 포함지 않도록 하는 것이다.
예를 들어, 위에서 email과 password만 들어오도록 되어있지만
1
2
3
4
5
| {
"email" : "importunate-dev@naver.com",
"password": "19940411",
"age": "30"
}
|
이라는 본문이 들어오면 age 속성은 제거한채 email과 password만 들어온다. 보안과도 관련있는 코드이다.