
모듈 컨트롤러 프로바이더는 외우는게 좋아
미들웨어
https://docs.nestjs.com/middleware
// 미들웨어의 세팅 (클래스) 추천!
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
// 미들웨어의 세팅 (함수도 가능하긴함)
//app.module 에서 연결! (추천)!
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*'); //전체(*) 라우터에 대해 하겠다.
}
}
// main.ts 의 app.use() 로 연결
async function bootstrap() {
const app = await NestFactory.create<INestApplication<Express>>(AppModule);
app.use(loggerMiddleware);
await app.listen(5050);
}
bootstrap();
import {
BadRequestException,
Injectable,
NestMiddleware,
UnauthorizedException,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { ParamsDictionary } from 'express-serve-static-core';
import { verify } from 'jsonwebtoken';
import { ParsedQs } from 'qs';
import { PrismaService } from './../db/prisma/prisma.service';
const JWT_SECRET_KEY = process.env.JWT_SECRET_KEY;
if (!JWT_SECRET_KEY) throw new BadRequestException('No JWT_SECRET_KEY');
@Injectable()
export class AuthMiddleware implements NestMiddleware<Request, Response> {
constructor(private readonly prismaService: PrismaService) {}
async use(
req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>,
res: Response<any, Record<string, any>>,
next: (error?: any) => void,
) {
req.user = null;
const accessToken = req.headers.authorization?.split('Bearer ')[1];
if (!accessToken) return next();
let id: number;
try {
const { sub } = verify(accessToken, process.env.JWT_SECRET_KEY);
id = Number(sub);
} catch (e) {
throw new UnauthorizedException('Invalid AccessToken'); // 이거를 써야 filter 에서 걸려
// throw new Error('Invalid AccessToken');
}
const user = await this.prismaService.user.findUnique({
where: { id: Number(id) },
});
if (!user) throw new BadRequestException('No User');
req.user = user;
next();
//인증관련 처리
// => 여권검사 == 토큰검사
//(토큰 있는지 확인
//=> 없다? => 일단 통과 but 아무것도 안해줌.
// => 있다? => 토큰 유효한지 확인
// => 유효 X? => 돌아가~ 에러 발생
// => 토큰 유효 => 유저 가져옴 => 유저 있나?
// => 유저 X? => 에러발생
// => 유저 O => 이후 프로세스에서 유저를 사용할 수 있도록 req.user 넣어준다.
}
}
후에 모듈에 연결해준다
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
}
가드
https://docs.nestjs.com/guards
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
두개의 방식 차이.
//공식 문서 Roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Roles } from './roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get(Roles, context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return matchRoles(roles, user.roles);
}
}
// LoggedInOnly.guard
@Injectable()
export class LoggedInOnlyGuard implements CanActivate {
constructor(private reflector: Reflector) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const loggedInOnly = this.reflector.get<boolean>(
'LoggedInOnly',
context.getHandler(),
); //
if (!loggedInOnly) return true; // 다 통과
const request = context.switchToHttp().getRequest<Request>();
if (!request.user) throw new ForbiddenException();
return true;
}
}
예외
https://docs.nestjs.com/exception-filters
//http-Exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = exception.getStatus();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
//main.ts
app.useGlobalFilters(new HttpExceptionFilter());
import { Controller, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from './path/to/http-exception.filter'; // Update the path as necessary
@Controller('some-path')
@UseFilters(HttpExceptionFilter)
export class SomeController {
// Controller methods
}
인터셉터
// 공식문서 transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(map(data => ({ data })));
}
}
=> 비동기적 요청들을 배열로 바꿔주는, ex) 5번째 순서에 뭐해주기 이런것들.
//transform.interceptor.ts data 반환 형식을 바꿔준것
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
success: true;
result: T;
error: null;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map((data) => ({
success: true,
result: data,
error: null,
})),
);
}
}
후에 전역 인터셉터로 사용하기 위해 main.ts 에 연결
///main.ts
app.useGlobalInterceptors(new TransformInterceptor());
커스텀 데코레이터
https://docs.nestjs.com/security/authentication
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
공부해야할것