본문 바로가기
개발/nest.js

nestjs + typeorm backend 서버 만들기 CRUD (4)

by 아크투어 2023. 3. 20.
반응형

1.  개요

  • nestjs18버전 + typeorm을 이용한 API백엔드 서버만들기 네번째 글이다.
  • 앞에(3)번글에서 DB연동을 하였으니 이번글에서는 테이블 생성 및 간단한 CRUD를 작성해 보겠다.
  • 작성할 CRUD는 User라는 회원 테이블 이다.

 

2.  최종소스트리구조

  • 아래 이미지참조

 

3.  User라는 CRUD 프로젝트 생성하기

  • 콘솔창 C:\nestjs\backend> 에서 nest g res {프로젝트명} 를 입력한다.
# user라는 CRUD 템플릿 생성
C:\nestjs\backend> nest g res user

# REST API생성
? What transport layer do you use? (Use arrow keys)
> REST API
  GraphQL (code first)
  GraphQL (schema first)
  Microservice (non-HTTP)
  WebSockets
  
# Y입력
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? (Y/n) Y

 

  • 아래와 같이 템플릿이 생성된다.
  • 생성된 파일중 user.controller.spec.ts / user.service.spec.ts 파일은 테스트 파일이라 지워도 무방하다.

 

 

4. typeorm.config.ts 파일에 User라는 entity 추가하기

  • src > config > database > typeorm.config.ts "User" 내용추가
# typeorm.config.ts

import { ConfigModule, ConfigService } from '@nestjs/config';
import {
  TypeOrmModuleAsyncOptions,
  TypeOrmModuleOptions,
} from '@nestjs/typeorm';

import { User } from '../../user/entities/user.entity';

export default class TypeOrmConfig {
  static getOrmConfig(configService: ConfigService): TypeOrmModuleOptions {
    return {
      type: configService.get<any>('db.postgres.type'),
      host: configService.get<string>('db.postgres.host') || 'localhost',
      port: configService.get<number>('db.postgres.port') || 5432,
      username: configService.get<string>('db.postgres.username'),
      password: configService.get<string>('db.postgres.password'),
      database: configService.get<string>('db.postgres.database'),
      entities: [User],
      migrations: ['src/config/database/migrations/*.ts'],
      migrationsTableName: 'migrations',
      synchronize:
        configService.get<boolean>('db.postgres.synchronize') || false,
      logging: true,
    };
  }
}

export const typeOrmConfigAsync: TypeOrmModuleAsyncOptions = {
  imports: [ConfigModule],
  useFactory: async (
    configService: ConfigService,
  ): Promise<TypeOrmModuleOptions> => TypeOrmConfig.getOrmConfig(configService),
  inject: [ConfigService],
};

 

 

5. typeorm관련 *.ts파일 추가하기

  • src > config > database > typeorm.decorator.ts 추가
# typeorm.decorator.ts

import { SetMetadata } from '@nestjs/common';

export const TYPEORM_CUSTOM_REPOSITORY = 'TYPEORM_CUSTOM_REPOSITORY';

export function CustomRepository(entity: Function): ClassDecorator {
  return SetMetadata(TYPEORM_CUSTOM_REPOSITORY, entity);
}

 

  • src > config > database > typeorm-custom.module.ts 추가
# typeorm-custom.module.ts

import { DynamicModule, Provider } from '@nestjs/common';
import { getDataSourceToken } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { TYPEORM_CUSTOM_REPOSITORY } from './typeorm.decorator';

export class TypeOrmCustomModule {
  public static forCustomRepository<T extends new (...args: any[]) => any>(
    repositories: T[],
  ): DynamicModule {
    const providers: Provider[] = [];

    for (const repository of repositories) {
      const entity = Reflect.getMetadata(TYPEORM_CUSTOM_REPOSITORY, repository);

      if (!entity) {
        continue;
      }

      providers.push({
        inject: [getDataSourceToken()],
        provide: repository,
        useFactory: (dataSource: DataSource): typeof repository => {
          const baseRepository = dataSource.getRepository<any>(entity);
          return new repository(
            baseRepository.target,
            baseRepository.manager,
            baseRepository.queryRunner,
          );
        },
      });
    }

    return {
      exports: providers,
      module: TypeOrmCustomModule,
      providers,
    };
  }
}

 

 

6. CRUD 코드작성

  • src > user > dto > create-user.dto.ts 추가
# create-user.dto.ts

export class CreateUserDto {
  userid: string;

  userpw: string;

  usernm: string;

  emailadres: string;

  mbtlno: string;

  age: number;
}

 

  • src > user > dto > update-user.dto.ts 추가
# update-user.dto.ts

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

 

  • src > user > entities > user.entity.ts 추가
# user.entity.ts

import {
  Column,
  Entity,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  Timestamp,
  UpdateDateColumn,
} from 'typeorm';

@Entity('users')
export class User {
  @PrimaryGeneratedColumn({ type: 'bigint' })
  private id: string;

  @Column('character varying', { name: 'userid', unique: true, length: 20 })
  private userid: string;

  @Column('character varying', { name: 'userpw', length: 255 })
  private userpw: string;

  @Column('character varying', { name: 'usernm', length: 100 })
  private usernm: string;

  @Column('character varying', { name: 'emailadres', length: 255 })
  private emailadres: string;

  @Column('character varying', { name: 'mbtlno', length: 20 })
  private mbtlno: string;

  @Column('int4', { name: 'age' })
  private age: number;

  @CreateDateColumn({
    type: 'timestamp without time zone',
  })
  private savedate: Timestamp;

  @UpdateDateColumn({
    type: 'timestamp without time zone',
  })
  private modifydate: Timestamp;

  constructor(
    userid: string,
    userpw: string,
    usernm: string,
    emailadres: string,
    mbtlno: string,
    age: number,
  ) {
    this.userid = userid;
    this.userpw = userpw;
    this.usernm = usernm;
    this.emailadres = emailadres;
    this.mbtlno = mbtlno;
    this.age = age;
    return this;
  }
}

 

  • src > user > user.controller.ts 추가
# user.controller.ts

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.userService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return this.userService.remove(+id);
  }
}

 

  • src > user > user.module.ts 추가
# user.module.ts

import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UserRepository } from './user.repository';
import { TypeOrmCustomModule } from '../config/database/typeorm-custom.module';

@Module({
  imports: [TypeOrmCustomModule.forCustomRepository([UserRepository])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

 

  • src > user > user.repository.ts 추가
# user.repository.ts

import { CustomRepository } from '../config/database/typeorm.decorator';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';

@CustomRepository(User)
export class UserRepository extends Repository<User> {}

 

  • src > user > user.service.ts 추가
# user.service.ts

import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserRepository } from './user.repository';
import { User } from './entities/user.entity';

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async create(dto: CreateUserDto) {
    const user = new User(
      dto.userid,
      dto.userpw,
      dto.usernm,
      dto.emailadres,
      dto.mbtlno,
      dto.age,
    );
    return this.userRepository.save(user);
  }

  async findAll() {
    return this.userRepository.find();
  }

  async findOne(id: number) {
    return this.userRepository.findOneBy({ id: id });
  }

  async update(id: number, dto: UpdateUserDto) {
    return this.userRepository.update(id, dto);
  }

  async remove(id: number) {
    return this.userRepository.delete(id);
  }
}

 

 

7. app.module.ts 확인

  • 소스코드
# app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfigAsync } from './config/database/typeorm.config';
import { UserModule } from './user/user.module';
import configuration from './config/profile/profile.config';

@Module({
  imports: [
    ConfigModule.forRoot({ load: [configuration] }),
    TypeOrmModule.forRootAsync(typeOrmConfigAsync),
    UserModule,
  ],
})
export class AppModule {}

 

 

8. users 테이블 생성

  • typeorm.config.ts 파일의 entities 에 포함된 내용을 기반으로 테이블을 자동생성한다.
  • dev.yaml의 db.postgres.synchronize를 true로 변경, true로 되어있을시 소스상의 엔티티파일 기준으로 계속 동기화 하겠다는의
  • 콘솔창 npm run start:dev 입력
  • 아래처럼 user.entity.ts 파일 기준으 테이블이 생성됨
# dev.yaml의 synchronize: true로 변경후 아래 실행
C:\nestjs\backend> npm run start:dev

[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] TypeOrmModule dependencies initialized +1ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
query: SELECT * FROM current_schema()
query: SELECT version();
query: START TRANSACTION
query: SELECT * FROM current_schema()
query: SELECT * FROM current_database()
query: SELECT "table_schema", "table_name" FROM "information_schema"."tables" WHERE ("table_schema" = 'public' AND "table_name" = 'users')
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = 'public' AND "table_name" = 'typeorm_metadata'
query: CREATE TABLE "users" ("id" BIGSERIAL NOT NULL, "userid" character varying(20) NOT NULL, "userpw" character varying(255) NOT NULL, "usernm" character varying(100) NOT NULL, "emailadres" character varying(255) NOT NULL, "mbtlno" character varying(20) NOT NULL, "age" integer NOT NULL, "savedate" TIMESTAMP NOT NULL DEFAULT now(), "modifydate" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_37b098e31baedfa2b76e7876998" UNIQUE ("userid"), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))
query: COMMIT
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] TypeOrmCoreModule dependencies initialized +305ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] TypeOrmCustomModule dependencies initialized +0ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [InstanceLoader] UserModule dependencies initialized +1ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RoutesResolver] UserController {/user}: +14ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RouterExplorer] Mapped {/user, POST} route +3ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RouterExplorer] Mapped {/user, GET} route +0ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RouterExplorer] Mapped {/user/:id, GET} route +1ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RouterExplorer] Mapped {/user/:id, PATCH} route +0ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [RouterExplorer] Mapped {/user/:id, DELETE} route +1ms
[Nest] 7624  - 2023. 03. 20. 오후 1:57:44     LOG [NestApplication] Nest application succes

 

  • 생성된 테이블 desc

 

 

9. 입출력 테스트

  • 등록

  • 수정

 

  • 조회

  • 삭제

 

 

10. 마치며

  • 아래와 같이 SQL 로그가 생성되어 있다.
  • 다음 포스팅에서는 nestjs cors, log, 인터셉터 등 간단한 튜닝및 개선 작업을 포스팅하도록 하겠다.

 

반응형