TypeORM (Object Relational Mapping)
TypeOrm 은 Node.js에서 사용하는 orm 라이브러리이며,
orm은 데이터베이스와 객체 지향 프로그래밍간의 매핑을 도와주는 도구이다.
다양한 데이터베이스를 지원하고, Entity 클래스를 통해 쉽게 테이블을 정의하고 관리가 가능하다.
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
이런식으로 엔티티 클래스를 만들어서 연결하면 테이블을 알아서 만들어준다.
설치
npm install @nestjs/typeorm typeorm sqlite3
난 sqlite 를 사용하기 위해 이렇게 설치를 해주었다.
모듈 설정
모듈에 임포트하는 예제를 보니 머리가 띵하다.
대충 데이터베이스를 설정하는건지는 알겠는데..
imports에는 그냥 모듈만 넣는 줄 알았는데 갑자기 함수를 사용한다.
여기서 동적 모듈이 등장한다.
동적 모듈
우리는 잘 만들어진 라이브러리를 가져다가 쓸 수 있지만, 쓰기 위해서는 사용자마다 설정이 다른 경우가 있다.
예를들면 기업이 자신들의 api 서비스를 제공하기 위해 모듈을 만든다면 사용자마다 비밀키가 다르기 때문에 그 비밀키를 설정해주는 작업을 모듈을 임포트 시킬때 해줄 수 있다.
TypeOrmModule 클래스를 들어가보면 DynamicModule 인터페이스를 리턴하는 static 함수 forRoot와 forRootAsync가 있다.
사용자마다 사용하는 DB가 다르고, 포트,아이디,비밀번호,엔터티와 같은 많은 설정들이 다르기 때문에,
TypeOrm을 사용하기 위해서는 우리의 루트 모듈에 한번 임포트를 해주고 실행할때 해당 정보로 연결을 하여 전역적으로 커넥션을 유지한다.
각 모듈에서 매번 DB에 연결을 하는것은 상당히 비합리적인 동작이기 때문이다.
register, forRoot, forFeature
https://docs.nestjs.com/fundamentals/dynamic-modules#dynamic-module-use-case
register, forRoot, forFeature
위 링크로 들어가보면 NestJS는 동적 모듈을 설정하는 함수에 대한 컨벤션 가이드라인을 제시한다.
해당 함수명은 TypeOrm에서 독자적으로 정한 함수명은 아니며 NestJS의 가이드라인을 잘 지킨 함수명이다.
아직 헷갈리긴 하지만 모듈을 동적 모듈로 사용할거라면 모듈의 기능에 따라서 해당 함수명을 다르게 지어야 한다.
내가 생각한 3가지 함수명의 쓰임새는 아래와 같다.
register: 여러 모듈에서 사용하며, 모듈마다 설정이 달라야 할 때
forRoot: TypeOrm 처럼 한번 설정한 모듈을 전역적으로 사용할 때
forFeature: forRoot로 설정한 동적 모듈의 다른 기능을 위해 입맞에 맞게 일부를 수정해야 하는경우
동적 모듈 만들어 보기
정말 간단하게 파일을 저장하는 모듈이 있다고 생각하고,
사용하는 모듈마다 저장하는 경로가 달라서 import할때 경로를 넣어주도록 만들었다.
file.module.ts
import { DynamicModule, Module } from '@nestjs/common';
import { FileService } from './file.service';
export interface fileModuleOptions{
path: string;
}
@Module({})
export class FileModule {
static register(options: fileModuleOptions): DynamicModule {
return {
module: FileModule,
providers: [
{
provide: 'FILE_OPTIONS',
useValue: options,
},
FileService,
],
exports: [FileService],
};
}
}
파일 모듈을 만들고 static 함수로 register 함수를 만들어 주었다.
반환은 꼭 DynamicModule 인터페이스여야 한다.
그리고 옵션으로 사용할 fileModuleOption 인터페이스를 만들어주고 인자값으로 넣어주었다.
module 에는 해당 모듈,
exports에는 내보낼 서비스를 넣어주었다.
그리고 옵션의 의존성 주입을 위해서 providers에 넣어주어야 하는데, 클래스를 providers에 넣었을때만 자동 DI가 가능하고 우리의 옵션을 넣어주기 위해서는 ValueProvider 인터페이스를 넣어서 DI 컨테이너에 InjectionToken을 전달해줘야 한다.
토큰은 'FILE_OPTIONS'로 정했다.
file.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { fileModuleOptions } from './file.module';
@Injectable()
export class FileService {
constructor(@Inject('FILE_OPTIONS') private options:fileModuleOptions){
}
useFileSystem(){
console.log(this.options.path);
return this.options.path;
}
}
모듈에서 넘긴 옵션을 서비스에서 주입받기 위해서는 아까 넘긴 토큰을 통해 받아주어야 한다.
@Inject 데코레이터를 사용하며, 아까 DI 컨테이너에 넣은 토큰을 적어준다.
useFileSystem 함수를 사용하면 설정한 경로를 로그로 찍는다.
@Module({
imports: [
FileModule.register({ path: '바탕화면' }),
],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
다른 모듈에서 파일 모듈을 임포트할때는 register를 통해 옵션을 넣어 줄 수 있다.
이제 파일 모듈은 사용하는 다른 모듈의 입맛에 맞춰 경로 설정이 가능하다.
다시 TypeOrm
앱 모듈
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: 'db.sqlite',
entities: [User],
synchronize: true,
}
),
UserModule,
FileModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
다시 앱 모듈로 돌아와서, TypeOrm모듈의 설정을 이런식으로 해주었다.
sqlite db를 사용하며, 데이터베이스 파일은 db.sqlite (루트 디렉토리 생성), 사용할 엔터티는 User
synchronize 옵션은 엔터티의 설정이 바뀌면 자동으로 db 테이블도 바꿔주는 옵션인데 개발단계에서만 사용하라고 강조하는 옵션이다.
유저 엔터티
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
firstName: string;
@Column()
lastName: string;
}
DB 테이블을 만들어줄 엔터티는 이런식으로 만들어주면 된다.
@Entity 데코레이터를 사용해서 User라는 엔터티를 만들었다.
@PrimaryGeneratedColumn 은 이름에서 알 수 있듯이 프라이머리 키 컬럼을 자동으로 생성해준다. (ex. id 1..2..3)
나머지는 @Column 데코레이터를 사용하면 된다.
TypeOrm을 사용할 모듈
@Module({
imports: [
TypeOrmModule.forFeature([User]),
],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
사용할 유저 모듈에도 TypeOrm모듈 설정을 해주어야 한다.
전역적으로 forRoot를 통해 DB 커넥션과 사용할 Entity들을 적어두었으니,
이제 TypeOrm을 사용할 현재 모듈에서 어떤 엔터티를 사용할건지 설정해주어야 한다.
(이 엔터티 설정은 해당 모듈이 어떤 엔터티를 사용할건지만 넣어주면 된다. 사용할 모든 엔터티는 처음 설정했던 forRoot에 넣어주어야 한다.)
TypeOrm을 사용할 서비스
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './Entity/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>
) {}
async getUser() {
const users = await this.userRepository.find();
if (users.length <= 0) {
throw NotFoundException;
}
return users;
}
}
위에 모듈에서 forFeature를 통해 사용할 엔터티를 설정을 해줬다면,
해당 서비스에서 레포지토리를 주입 받을 수 있다.
@InjectRepository 데코레이터를 통해 사용하면 되며,
모든 DB 통신은 해당 변수를 통해 이루어진다.
createUser(firstName: string, lastName: string) {
const newUser = this.userRepository.create({ firstName, lastName });
this.userRepository.save(newUser);
return newUser;
}
이런식으로 유저DB를 생성하는 함수를 만들었다.
이런식으로 저장이 잘 되었다.
해당 익스텐션을 깔면 쉽게 볼 수 있다.
'서버 > NestJs' 카테고리의 다른 글
[NestJS] Interceptor (0) | 2024.10.24 |
---|---|
[NestJs] 데코레이터,컨트롤러,서비스,모듈 (0) | 2024.10.23 |
[NestJs] NestJs 시작하기 (2) | 2024.10.23 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!