본문 바로가기

Programming Language/TypeScript

[NetsJS] TypeORM 기본 CRUD 구현하기

 








* Github : https://github.com/choseongho93/study/tree/main/TypeORM



★ TypeORM 설치

npm install --save @nestjs/typeorm typeorm pg





.env.dev

DB_HOST=localhost 
DB_PORT=5432 
DB_USERNAME=postgres 
DB_PASSWORD=1234 
DB_NAME=test_db

.env.dev파일을 생성해서 환경변수를 작성해줍니다.



src/app.module.ts

생략...

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: process.env.NODE_ENV === 'dev' ? '.env.dev' : '.env.test',
      ignoreEnvFile: process.env.NODE_ENV === 'prod',
      validationSchema: Joi.object({
        NODE_ENV: Joi.string()
          .valid('dev', 'prod')
          .required(),
        DB_HOST: Joi.string().required(),
        DB_PORT: Joi.string().required(),
        DB_USERNAME: Joi.string().required(),
        DB_PASSWORD: Joi.string().required(),
        DB_NAME: Joi.string().required(),
      }),
    }),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
      port: +process.env.DB_PORT,
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      synchronize: process.env.NODE_ENV !== 'prod',
      logging: process.env.NODE_ENV !== 'prod',
      entities: [User], // Entity를 넣어준다
    }),
    UsersModule,
  ],
  controllers: [],
  providers: [],
})

생략...

AppModule 데코레이터의 imports에 ConfigModule(환경변수 설정)객체와 TypeOrmModule 객체의 forRoot를 통해 작성해줍니다. TypeOrmModule 객체에서 접속정보와 커넥션 옵션을 작성해줍니다. 이때 포트는 +를 한이유가 int로 변환해주기 위함입니다.

+ ConfigModule은 joi를 통해 환경변수 유효성 검사를 진행해주며 package.json파일에서 npm run에 따라 dev와 prod환경에 따라 처리되도록 분기처리됩니다. 여기서는 dev 개발환경에서 실행하였습니다.
즉, 개발환경으로 작업해주시면 됩니다.

@nestjs/config을 별도로 npm 설치해주셔야 합니다.


 

 

 

 

 





/src/entities/user.entity.ts (User Entity 생성)

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

@Entity()
export class User {

  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  userId: string;

  @Column()
  userName: string;

  @Column()
  userPassword: string;

  @Column()
  age: number;

  @Column({ default: true })
  isActive: boolean;

}

위처럼 User Entity를 만들었습니다. Entity는 DB table에 대한 구조라고 생각하시면 됩니다.
AppModule에서 TypeOrmMoule에 entities User를 입력해주어야 합니다. (src/app.module.ts 참고)
AppModule에 등록 후 실제 사용하게 될 단위 모듈 위치인 userModeul에서 아래와 같이 imports를 통해 해당 Entity 객체를 setting 해줍니다.




src/user/user.module.ts

생략...

@Module({
    imports:[TypeOrmModule.forFeature([User])], // 생성한 Entity를 넣어준다
    controllers: [UserController],
    providers: [UserService],
})
export class UsersModule {}

생략...

Repository 디자인 패턴을 사용하기 위해 UserService를 provider에 넣어주고, UserController는 controllers에 세팅해줍니다.
UserModule에서 기본적으로 CRUD를 할 세팅을 진행하였습니다.





src/user/user.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm/index';

@Injectable()
export class UserService {
  /**
   * 생성자
   */
  constructor(
    @InjectRepository(User) private userRepository: Repository<User>,
  ) {
    this.userRepository = userRepository;
  }
  /**
   * User 리스트 조회
   */
  findAll(): Promise<User[]> {
    return this.userRepository.find();
  }
  /**
   * 특정 유저 조회
   * @param id
   */
  findOne(id: number): Promise<User> {
    return this.userRepository.findOne({ id: id });
  }
   /**
   * 유저 수정
   * @param user
   */
    async updateUser(id: number, user: User): Promise<void> {
      await this. userRepository.update(
        id ,
        user
      );
    }
  /**
   * 유저 저장
   * @param user
   */
  async saveUser(user: User): Promise<void> {
    await this.userRepository.save(user);
  }
  /**
   * 유저 삭제
   */
  async deleteUser(id: number): Promise<void> {
    await this.userRepository.delete({ id: id });
  }
}

User Repository 디자인 패턴을 통해 코드를 작성했습니다.
InjectRepository 데코레이터를 통해서 User Entity를 userRepository에 담아 UserService 클래스에 주입하여 사용합니다.
기본적으로 Entity를 만들고 AppModule에 TypeOrm dynamic module을 통해서 커넥션 정보와 Entities에 user를 등록하였습니다. 그리고 실제 사용하려는 UserModule에서 User Entity를 TypeOrm dynamic module의 forFeature를 통해 등록했습니다. 이 모든 과정이 setup이 되어야 위 레파지토리로 의존성 주입이 가능하고 해당 레파지토리를 통해 CRUD를 구현할 수 있습니다.




src/user/user.controller.ts

import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { UserService } from './users.service';
import { User } from './entities/user.entity';
@Controller('user')
export class UserController {
  constructor(
    private userService: UserService
  ) {
    this.userService = userService;
  }

  @Get('list')
  async findAll(): Promise<User[]> {
    const userList = await this.userService.findAll();
    return Object.assign({
      data: userList,
      statusCode: 200,
      statusMsg: `데이터 조회가 성공적으로 완료되었습니다.`,
    });
  }

  @Get(':id')
  async findOne(@Param('id') id: number): Promise<User> {
    const foundUser = await this.userService.findOne(+id);
    return Object.assign({
      data: foundUser,
      statusCode: 200,
      statusMsg: `데이터 조회가 성공적으로 완료되었습니다.`,
    });
  }

  @Patch(':id')
  async updateUser(@Param('id') id: number, @Body() user: User): Promise<string> {
    await this.userService.updateUser(id, user);
    return Object.assign({
      data: { ...user },
      statusCode: 200,
      statusMsg: `updated successfully`,
    });
  }

  @Post()
  async saveUser(@Body() user: User): Promise<string> {
    await this.userService.saveUser(user);
    return Object.assign({
      data: { ...user },
      statusCode: 200,
      statusMsg: `saved successfully`,
    });
  }

  @Delete(':id')
  async deleteUser(@Param('id') id: string): Promise<string> {
    await this.userService.deleteUser(+id);
    return Object.assign({
      data: { userId: id },
      statusCode: 200,
      statusMsg: `deleted successfully`,
    });
  }
  
}

위 UserController.ts파일에서는 route같은 역할을 하며 UserService에 실제 비즈니스 로직이 들어있고 그 메소드를 호출해 구현했습니다.
테스트 코드로 작성하면서 Response하는 객체를 공통화 시키지 않고 각 function별로 Object.assign을 통해 JSON String으로 반환하고 있습니다.
CRUD를 통해 데이터 전체 조회/ 특정 데이터 조회 / 수정 / 삽입 / 삭제를 구현했습니다.


 

 

 

 

 





 

데이터 삽입

  /**
   * 유저 저장
   * @param user
   */
  async saveUser(user: User): Promise<void> {
    await this.userRepository.save(user);
  }

위 코드는 데이터 삽입 부분으로써 TypeORM을 통해 save라는 함수로 api 호출할떄 body로 받은 json형식의 데이터를 user변수에 받아 그대로 파라미터로 전달해서 저장합니다.
async는 비동기 처리입니다.



리스트 전체 조회 

  /**
   * User 리스트 조회
   */
  findAll(): Promise<User[]> {
    return this.userRepository.find();
  }

위 코드는 "select * from user;"와 같은 것이며 Native Query가 존재하지 않고 이미 추상화 되어있는 TypeOrm의 find() 메소드를 통해 Vendor가 MySQL, Postgres, MariaDB, Mongoose 등 어떠한 것을 사용하던간에 select를 할 수 있습니다.
where 조건은 제외하였기에 위와같이 작성되어있지만, 전부 TypeORM에서 Join과 같은것들도 제공하고 있습니다.

 

데이터 수정

   /**
   * 유저 수정
   * @param user
   */
    async updateUser(id: number, user: User): Promise<void> {
      await this. userRepository.update(
        id ,
        user
      );
    }

위 코드는 데이터 삽입 부분으로써 TypeORM을 통해 update라는 함수로 api 호출할떄 body로 받은 json형식의 데이터를 user변수에 받고 수정할 primarykey인 id를 param으로 받아 조건에 넣어줍니다.

 

 

 

 

 

 




특정 데이터 조회 

  /**
   * 특정 유저 조회
   * @param id
   */
  findOne(id: number): Promise<User> {
    return this.userRepository.findOne({ id: id });
  }

위 코드는 "select * from user where id = ?;"와 같은 것이며 Native Query가 존재하지 않고 이미 추상화 되어있는 TypeOrm의 findOne() 메소드를 통해 조건을 넣어 select합니다.




데이터 삭제 

  /**
   * 유저 삭제
   */
  async deleteUser(id: number): Promise<void> {
    await this.userRepository.delete({ id: id });
  }

위 코드는 데이터 삽입 부분으로써 TypeORM을 통해 delete라는 메소드를 호출해 id를 가진 유저를 데이터 삭제합니다.