3-Layered Architecture를 분리하고나서 마주한 두가지 문제점
1) 에러처리는 Controller에서 다 하는데, 미들웨어에서도 에러처리를 해도 되는건가..?
2) Service 계층에서 모든 에러를 throw new Error 로 처리하니까 콘솔창에만 에러메시지가 뜨고 insomnia에서는 안뜬다..
근데 이걸 모두 한번에 해결해주는 방법이 있었음. 에러를 담을 클래스를 생성하여, 에러가 발생하는 위치에 throw new (클래스이름) 형식으로 객체를 만들어 next로 던져주면 된다.
그러니까,
// ./error/http-error.js
import { HTTP_STATUS } from '../const/http-status.const.js';
class BadRequest {
constructor(message = BadRequest.name) {
this.message = message;
this.status = HTTP_STATUS.BAD_REQUEST;
}
}
class Unauthorized {
constructor(message = Unauthorized.name) {
this.message = message;
this.status = HTTP_STATUS.UNAUTHORIZED;
}
}
.
.
export const HttpError = {
BadRequest,
Unauthorized,
Forbidden,
NotFound,
Conflict,
InternalServerError,
};
이런식으로 에러 status별 클래스를 만들어 export 해주고,
// ./service/auth.service.js
import { AuthRepository } from '../repositories/auth.repository.js';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { MESSAGES } from '../const/messages.const.js';
import { HttpError } from '../error/http.error.js';
export class AuthService {
authRepository = new AuthRepository();
.
.
//로그인
signIn = async (email, password) => {
const user = await this.authRepository.findUserInfoByEmail(email);
if (!user) {
throw new HttpError.NotFound(MESSAGES.AUTH.SIGN_IN.IS_NOT_EXIST);
}
if (!(await bcrypt.compare(password, user.password))) {
throw new HttpError.Unauthorized(MESSAGES.AUTH.SIGN_IN.PW_NOT_MATCHED);
}
const { accessToken, refreshToken } = await this.refreshToken(user.userId);
return { accessToken, refreshToken };
};
.
.
}
throw new Error 대신 throw new HttpError.NotFound('에러메시지') 라고 작성하면 status와 에러메시지가 모두 에러처리 미들웨어로 전달된다.
// ./middleware/access-token.middleware.js
import jwt from 'jsonwebtoken';
import { AuthRepository } from '../repositories/auth.repository.js';
import { MESSAGES } from '../const/messages.const.js';
import { HttpError } from '../error/http.error.js';
export default async function (req, res, next) {
try {
const authRepository = new AuthRepository();
const authorization = req.headers.authorization;
if (!authorization) {
throw new HttpError.BadRequest(MESSAGES.JWT.NONE);
}
const [tokenType, accessToken] = authorization.split(' ');
if (tokenType !== 'Bearer') {
throw new HttpError.Unauthorized(MESSAGES.JWT.NOT_TYPE);
}
if (!accessToken) {
throw new HttpError.Unauthorized(MESSAGES.JWT.NONE);
}
const decodedToken = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET_KEY);
const userId = decodedToken.id;
const user = await authRepository.findUserInfoById(userId);
if (!user) {
throw new HttpError.Unauthorized(MESSAGES.JWT.NO_MATCH);
}
req.user = user;
next();
} catch (err) {
next(err);
}
}
미들웨어도 마찬가지로 이런식으로 하면 된다. next(err)로 넘어간 나머지 에러들은 에러처리 미들웨어에서 다룬다.
// ./middlewares/error-hander.middleware.js
import { MESSAGES } from '../const/messages.const.js';
import { HTTP_STATUS } from '../const/http-status.const.js';
export default function (err, req, res, next) {
console.error(err);
if (err.name === 'ValidationError') {
return res.status(HTTP_STATUS.BAD_REQUEST).json({
status: HTTP_STATUS.BAD_REQUEST,
message: err.message,
});
} else if (err.name == 'TokenExpiredError') {
return res.status(HTTP_STATUS.UNAUTHORIZED).json({
status: HTTP_STATUS.UNAUTHORIZED,
message: MESSAGES.JWT.EXPIRED,
});
} else if (err.name == 'JsonWebTokenError') {
return res.status(HTTP_STATUS.UNAUTHORIZED).json({
status: HTTP_STATUS.UNAUTHORIZED,
message: MESSAGES.JWT.NOT_AVAILABLE,
});
}
return res.status(err.status).json({
status: err.status,
message: err.message,
});
}
에러핸들러 미들웨어에 jwt 관련 에러를 if로 처리한 결과.
'TIL' 카테고리의 다른 글
TIL 240614 - 개인과제 해설영상 참고하여 리팩토링 (1) | 2024.06.14 |
---|---|
TIL 240613 - JEST 테스트코드 작성 시 에러 검증 방법 (0) | 2024.06.13 |
TIL 240611 - 알고리즘 코드카타 리뷰 - 신고 결과 받기 (1) | 2024.06.11 |
TIL 240610 - 계층형 아키텍처 패턴 (1) | 2024.06.10 |
TIL 240607 - nodemailer (1) | 2024.06.07 |