다음 프로젝트에서 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 |