본문 바로가기

공부기록/42 Seoul

NestJS + PostgreSQL + TypeORM CRUD

지금까지 DB 로 해본거라곤 node.js와 MySQL 을 연동하기 위해

npm의 mysql 모듈을 설치해서 SQL문을 바로 쓰는 이런거 밖에 안해본 프론트엔드 지망생인데,,,

const db = require('../db/dbconnection');

db.query(
  `UPDATE user SET ${weekNum} = ${weekNum} + 1 WHERE user_id="${user_id}"`,
  //...대충 콜백함수 자리
);

 

 

갑자기 NestJS 와 ORM의 세계에 던져졌다.

먼저 ORM과 TypeORM이 뭔지 검색해서 알아봤다.

ORM
ORM은 Object Relational Mapping 즉, 객체-관계 매핑의 줄임말이다. 객체-관계 매핑을 풀어서 설명하자면 우리가 OOP(Object Oriented Programming)에서 쓰이는 객체라는 개념을 구현한 클래스와 RDB(Relational DataBase)에서 쓰이는 데이터인 테이블 자동으로 매핑(연결)하는 것을 의미한다. 그러나 클래스와 테이블은 서로가 기존부터 호환가능성을 두고 만들어진 것이 아니기 때문에 불일치가 발생하는데, 이를 ORM을 통해 객체 간의 관계를 바탕으로 SQL문을 자동으로 생성하여 불일치를 해결한다. 따라서 ORM을 이용하면 따로 SQL문을 짤 필요없이 객체를 통해 간접적으로 데이터베이스를 조작할 수 있게 된다.


출처: https://geonlee.tistory.com/207 [빠리의 택시 운전사]
ORM의 장단점은 읽어보긴 했는데 와닿진 않는다.. 직접 쓰면서 느껴보자.
TypeORM
TypeORM은 Node.js위에서 작동하며 TypeScript를 사용할 수 있는 ORM라이브러리이다.
entities, repository, columns, relations, replication, indices, queries, logging등 다양한 기능을 정의하고 관리할 수 있다.
TypeORM은 Active Record패턴과 Data Mapper 패턴을 지원한다.
🔗 Active Record 패턴과 Data Mapper 패턴에 대하여
결합도가 낮고 확장가능한 높은 수준의 어플리케이션을 작성할 수 있게 한다.
TypeORM 샘플코드
// Ashley가 작성하고 상태가 "DONE"인 task들을 가져온다.

// TypeORM
const tasks = await Task.find({ status: 'DONE', user: 'Ashley' })

 

 


 

transcendence 과제를 위한 첫번째 milestone

1. NestJS와 PostgreSQL 를 연동해서 user 엔티티 만들기

2. TypeORM 이용해 Database에 User CRUD 구현

 

⚠️ 주의
아직 진행중인 프로젝트라 내용이 허접할 수 있음.

postgreSQL 설치

$ brew install postgresql
$ postgres -V
postgres (PostgreSQL) 13.3

$ postgresql 서비스 시작. -D 뒤에 있는 경로는 datadir 경로. postgres 접속해서 show data_directory; 명령으로 확인가능

$ 재부팅할 때 마다 postgres 서비스를 시작해야하는 번거로움을 없앨 수 있는 명령. brew services --help 참고

$ pg_ctl -D /usr/local/var/postgres start
$ brew services start postgresql

postgres에 접속해서 필요한 database 생성

$psql postgres

postgres=# \l
postgres=# create database 데이터베이스명
# 사용할 데이터베이스 선택
postgres=# \c 데이터베이스명 사용자명

# 접속한 데이터베이스의 테이블 보기
postgres=# \dt

# 특정 테이블의 상세정보 확인
postgres=# \d 테이블명

 

필요한 패키지 설치

$ npm install pg typeorm @nestjs/typeorm
  • pg : nestJS 와 postgresql을 연동할 수 있는 모듈. mysql npm 모듈과 비슷.
  • typeorm 
  • @nestjs/typeorm 

 

ormconfig.json 파일 생성

{
  "type": "postgres",
  "host": "localhost",
  "post": "5432",
  "username": "hysimok",
  "password": "",
  "database": "t11e",
  "synchronize": true,
  "logging": true,
  "entities": ["dist/**/*.entity.{ts,js}"]
}

 

user 모델 생성

$ nest g mo user
$ nest g co user
$ nest g s user

 

app 모듈에 TypeOrm 모듈 연결

// src/app.module.ts
// ...
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [UserModule, TypeOrmModule.forRoot()],
  controllers: [AppController],
  providers: [],
})
// ...

 

엔티티 작성

다른 컬럼들은 나중에 추가하기로 하고

일단 id, intra_id, nickname 컬럼만 추가해봤다.

id 는 autoincrement 되는 번호가 아닌, 42 intra 내부의 고유한 id값을 넣어줄 예정이라 @PrimaryColumn() 을 사용했다.

// src/user/entities/user.entity.ts
import { Column, Entity, PrimaryColumn } from 'typeorm';

@Entity('users')
export class User {
  @PrimaryColumn()
  id: number;

  @Column()
  intra_id: string;

  @Column()
  nickname: string;
}

 

결과

postgres에 접속해서 확인해보면 테이블과 컬럼들이 생성된 것을 볼 수 있다.


User CRUD 구현

이제 TypeORM에서 제공하는 repository 패턴을 이용해 DB 에 CRUD 를 해볼 차례이다.

 

먼저 forFeature() 메서드를 이용해, 이 repository의 스코프를 users 모델로 한정한다.

users 모델과 관련된 서비스를 외부에서도 이용할 수 있게 하려면 exports 로 내보내줘야한다. (나중에 Auth 모듈에서 이용해야함)

// users.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/users.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})

export class UsersModule {}

 

// users.controller.ts
// ...
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  // READ
  @Get()
  getAllUser() {
    return this.usersService.findAll();
  }

  @Get(':id')
  getOneUser(@Param('id') userId: number) {
    return this.usersService.findOne(userId);
  }

  // CREATE
  @Post()
  createUser(@Body() req) {
    return this.usersService.createUser(req);
  }

  // DELETE
  @Delete(':id')
  removeUser(@Param('id') userId: number) {
    return this.usersService.remove(userId);
  }

  // UPDATE
  @Patch(':id')
  updateUser(@Param('id') userId: number, @Body() req) {
    return this.usersService.update(userId, req);
  }
  
  //...
}

repository 를 이용하면 find(), create(), save() 와 같은 Repository API 메서드들을 활용할 수 있어 편리하다.

// users.service.ts

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

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> {
    return this.usersRepository.find();
  }

  async findOne(userId: number): Promise<User> {
    const user: User = await this.usersRepository.findOne(userId);
    if (!user) {
      throw new NotFoundException(`${userId} not found.`);
    }
    return user;
  }

  async createUser(req) {
    const newUser: User = this.usersRepository.create({
      id: req.id,
      intra_id: req.intra_id,
      nickname: req.nickname,
    });
    await this.usersRepository.insert(newUser);
  }

  async remove(id: number): Promise<void> {
    await this.usersRepository.delete(id);
  }

  async update(id: number, req): Promise<User> {
    const userToUpdate: User = await this.findOne(id);
    userToUpdate.nickname = req.nickname;
    return await this.usersRepository.save(userToUpdate);
  }
}

update 는 아직 UserDto 를 정의하기 전이라 그냥 닉네임만 바꾼다고 가정하고 간단하게 진행

 

POST 테스트

GET 메서드

다른 메서드들 테스트 생략

 

 

TypeORM 을 이용해 transaction 을 하는 방법에는 Active Record, Data Mapping 방식이 있는데 일단 비교해보지 않고  repository 를 활용해서 DB와 데이터를 주고받아봤다. Connection 도 활용하지 않았는데 나중에 필요할 때 도입하면서 해보는걸로...

 

나중에 할거

  • UserDto 만들기(?)
  • migration 도입
  • postgres DB dockerize

'공부기록 > 42 Seoul' 카테고리의 다른 글

[NestJS] 42OAuth 인증하기  (0) 2021.08.01
Transcendence 공부 리소스  (0) 2021.07.11
42 과제에서 배운 것 정리  (0) 2021.05.29
[ft_services] nginx 설정  (0) 2021.02.01
[ft_services] MetalLB 설정  (2) 2021.01.19