Back/Node.js

NestJS TypeORM 간단 예제

다음 프로젝트에서 NestJS를 실무 수준에서 사용할 수 있어야해서, 그만 공부하고 실제로 만들어봤다.

공식문서도 잘 되어 있고, 실제로 NestJs로 작업하고 있는 개발자들에게도 도움을 받아 적응은 조금씩 하고 있다.

역시 직접 만들어 보는게 이해하는데 빠르다.

공식 문서에 나온 요청의 흐름이다.
이 모든 순서를 다 적용해서 예제 라우터를 짜진 않았지만 대강 흐름은 이해했다.
더 자세히 이해하려고, 동료 개발자(천재)와 함께 TypeORM을 이용해서 가벼운 CRUD를 만들어봤다.

schedule.controller.ts

import { Body, Controller, Post, UseGuards, UseInterceptors } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Users } from 'output/entities/Users';
import { CurrentUser } from 'src/common/decorators/currentUser.decorator';
import { SuccessInterceptor } from 'src/common/interceptors/success.intercept';
import { JwtAuthGuard } from 'src/common/jwt/jwt.guard';
import { ScheduleDto } from '../dto/schedule.dto';
import { ScheduleService } from '../service/schedule.service';

@ApiTags('스케줄')
@Controller('schedule')
@UseInterceptors(SuccessInterceptor)
export class ScheduleController {
  constructor(private readonly scheduleService: ScheduleService) {}

  @UseGuards(JwtAuthGuard)
  @Post()
  join(@Body() data: ScheduleDto, @CurrentUser() user: Users) {
    return this.scheduleService.makeTimetable(data, user);
  }
}

 

swagger을 Tag만 쓰고 제대로 이용하지 않았지만, 실제 프로젝트에선 제대로 이용하려 한다.

적어도 INSOMNIA, POST MAN같은 툴보단 이게 더 편할 듯  싶다.

물론 회사 내에서의 API 정리 문서도 가지고 있어야겠다.

 

컨트롤러의 흐름은
먼저SuccessInterceptor가 작동한다.

success.intercept.ts

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class SuccessInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // 여기에 라우터가 발동
    console.log('Intercept success');
    return next.handle().pipe(
      map((data) => ({
        success: true,
        data,
      })),
    );
  }
}

저 사이에 라우터가 발동되며, 성공시에 success: true와 함께 data를 내려 보낸다.

실패시엔 에러 코드를 같이 내보낸다.
nestjs는 암막개발? 용어가 기억 안나는데, 앞이 안보이는 개발이다.
뜯어보지 않는 이상 정확히 왜 돌아가는지 모르는 것들이 정말 많다.
그래서 나중에 시간이 나면 다 뜯어볼 생각이다.

인터셉터가 발동된 뒤엔
@UseGuard에 있는 JwtAuthGuard가 발동

jwt.strategy.ts

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'output/entities/Users';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersRepository } from 'src/users/users.repository';
import { Payload } from './jwt.payload';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(Users)
    private readonly usersRepository: UsersRepository,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secretKey',
      ignoreExpiration: false,
    });
  }

  async validate(payload: Payload): Promise<Users> {
    const user = await this.usersRepository.findOne({ userIdx: payload.user });
    console.log('payload', payload);

    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

jwt.strategy.ts

레포지토리 패턴을 이용했으며, InjectRepository에 Users엔티티를 불러온다.
기존 로그인 성공시 발급 받았던 토큰으로 userIdx를 검증한다.

없으면 에러를 thorw 하고 있으면 Db에서 꺼내온 user를 리턴해준다.

기존 express에선 authroizaiton : Beare 토큰 이런 식으로 헤더에서 받아오고 jwt 검증하고 그랬는데,
여기선 뭐 알아서 다 해주니까.. 신기하기도 하고 믿어도 되나 싶기도 했다.
나중에 꼭 뜯어봐야지. 

 

jwt에서 넘겨준 user는 컨트롤러에서 받을 수 있다.
guard는 말그대로 가드만한것이고, 컨트롤러 내에서 user정보를 받고 서비스로 넘겨줘야 하기 때문에,
커스텀 데코레이터를 만들었다.


currentUser.decorator

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

request에 담긴 user정보를 받아오는 커스텀 데코레이터다.

jw가드에서 user를 리턴했고, 리턴된 user는 request에 담겨오기 때문에, 그걸 받아오는 것이다.

그래서 유저가 보낸 data, user를 모두 서비스로 보낸다.

 

schedule.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Timetable } from 'output/entities/Timetable';
import { TimetableDetail } from 'output/entities/TimetableDetail';
import { Users } from 'output/entities/Users';
import { Repository } from 'typeorm';
import { ScheduleArrayDetailDto } from '../dto/schedule-array-detail.dto';
import { ScheduleDto } from '../dto/schedule.dto';

@Injectable()
export class ScheduleService {
  constructor(
    @InjectRepository(Timetable)
    private scheduleRepository: Repository<Timetable>,
    @InjectRepository(TimetableDetail)
    private scheduleDetailRepository: Repository<TimetableDetail>,
  ) {}

  async makeTimetable(data: ScheduleDto, user: Users) {
    const title = (await (
      await this.scheduleRepository.insert({ title: data.title, userIdx: user.userIdx })
    ).generatedMaps[0]) as { timetableIdx: number };
    console.log(title.timetableIdx);
    const detail: ScheduleArrayDetailDto[] = data.detailArray.map((item) => {
      return {
        ...item,
        timetableIdx: title.timetableIdx,
        userIdx: user.userIdx,
      };
    });
    await this.scheduleDetailRepository.insert(detail);
  }
}

테이블 두 곳에 넣어야하고,

A테이블은 하나의 데이터를 넣고,

B테이블은 방금 넣은 A테이블의 PK와 함께 불특정 여러개의 데이터를 넣어야했다.
둘 모두 user정보를 함께 저장해야 했기 때문에, 컨트롤러에서 커스텀데코레이트에서 받아온 user정보도 같이 보내줬다.

잘 들어간다.

익스프레스만 하다가 질려서 죽는 줄 알았는데 네스트 재밌다.

'Back > Node.js' 카테고리의 다른 글

[Node.js] Apple Login Passport 애플 로그인  (0) 2021.12.14
[NestJs] Prisma2 셋팅  (0) 2021.11.23
[Node.js] express 폴더 구조 변경  (0) 2021.10.17
AWS RDS AURORA + NODEJS  (0) 2021.10.15
Nodejs Excel Mysql  (0) 2021.10.04