throw new Error(‘BadRequest’)
자바스크립트에서 Error를 던져서 에러 처리하는 것은 쉽고 간단한 방법이다. express에서도 마찬가지이다. Error를 던지면 200 OK가 아닌 500 Internal Server Error를 발생시킬 수 있다.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
throw new Error('BadRequest');
});
app.listen(3000, () => { console.log('listen'); });
요즘 세상에 에러났다고 무조건 500을 던지면 멍청한 REST API처럼 보인다.
상황에 맞춰서 4xx, 5xx를 던져야한다.
500 아닌 상태 코드를 보내고 싶으면 res.status()
를 사용하면 된다.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.status(400).json({ text: 'todo' });
});
app.listen(3000, () => { console.log('listen'); });
나는 둘을 합치고 싶다.
BadRequest
라는 에러를 던지면 400,
NotFound
라는 에러를 던지면 404가 HTTP status code로 찍히게 만들고 싶다.
짧은 방법
http-errors를 사용하면 간단하다. 바퀴는 새로 발명하는게 아니다.
const express = require('express');
const createError = require('http-errors');
const app = express();
app.get('/', (req, res) => {
throw new createError.BadRequest();
});
app.listen(3000, () => { console.log('listen'); });
알아봐서 별 쓸모없는 정보
심심한 사람만 읽어보자. 당장 눈 앞의 문제 해결에는 도움이 되지 않는 내용이다. 하지만 알고있으면 자신의 문제에 응용할 수 있을 것이다.
왜 throw new Error()
를 쓰면 안되는가?
throw new Error()
는 편한 도구이다.
http-errors
를 깔고 import하지 않아도 에러를 만들어서 던질 수 있지 않는가?
게다가 에러가 던져지면 200이 뜨진 않는다.
REST API라고 부르기에는 멍청하지만 대충 쓸만하다.
하지만 sentry가 붙으면 문제가 커진다.
https://github.com/getsentry/sentry-javascript/blob/v3.27.2/packages/node/src/handlers.ts#L270
export function errorHandler(): (
error: MiddlewareError,
req: http.IncomingMessage,
res: http.ServerResponse,
next: (error: MiddlewareError) => void,
) => void {
return function sentryErrorMiddleware(
error: MiddlewareError,
_req: http.IncomingMessage,
_res: http.ServerResponse,
next: (error: MiddlewareError) => void,
): void {
const status = getStatusCodeFromResponse(error);
if (status < 500) {
next(error);
return;
}
const eventId = captureException(error);
(_res as any).sentry = eventId;
next(error);
};
}
Official Sentry SDKs for JavaScript의 경우 HTTP 상태 코드가 5xx이면 에러 리포팅 올리는게 기본값이다.
throw new Error
로 던진 에러가 전부 sentry에 등록된다.
이는 당신이 의도한 동작이 아닐 것이다.
왜 기본값이 500인가?
express는 finalhandler를 기본 에러 핸들러도 사용한다. application.js
app.handle = function handle(req, res, callback) {
var router = this._router;
// final handler
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
...
finalhandler의 README를 참고하면 500이 되는 이유를 알 수 있다.
The res.statusCode is set from err.status (or err.statusCode). If this value is outside the 4xx or 5xx range, it will be set to 500.
4xx, 5xx는 어떻게 던질수 있는가?
http-errors
쓰는 대신 에러 클래스를 직접 구현하고 싶은 생각이 들지 모른다.
http-errors
를 직접 만들어보자.
위에서 언급한 finalhandler의 README를 보면 구현할 수 있다.
에러 객체에 status
또는 statusCode
로 원하는 상태 코드를 넣어준다.
const express = require('express');
const app = express();
app.get('/', (req, res) => {
const e = new Error('sample');
e.status = 400;
throw e;
});
app.listen(3000, () => { console.log('listen'); });
라이브러리에서 던져진 에러는 어떻게 제어할 수 있는가?
Error에 status, statusCode 넣는건 자바스크립트 세상의 표준이 아니다. 그래서 많은 라이브러리의 에러 클래스에는 status, statusCode 속성이 없다.
export default function ValidationError(errors, value, field, type) {
this.name = 'ValidationError';
this.value = value;
this.path = field;
this.type = type;
this.errors = [];
this.inner = [];
// ...
this.message =
this.errors.length > 1
? `${this.errors.length} errors occurred`
: this.errors[0];
if (Error.captureStackTrace) Error.captureStackTrace(this, ValidationError);
}
jsonwebtoken/JsonWebTokenError
var JsonWebTokenError = function (message, error) {
// ...
this.name = 'JsonWebTokenError';
this.message = message;
if (error) this.inner = error;
};
위와 같은 라이브러리를 사용하는데 에러가 던져지면 어떻게 대응할까?
catch
를 통해서 라이브러리에서 던져진 에러를 잡은 후 http-errors
로 다시 던지는 것도 방법이다.
하지만 기존 코드를 전부 catch
로 감싸는건 너무 무식하다.
express middleware를 사용하면 조금 우아하게 처리할 수 있다.
에러 핸들러에서 err.name
를 확인하고 원하는 에러를 대신 던지는 식으로 처리할 수 있다.
const express = require('express');
const createErrors = require('http-errors');
const jwt = require('jsonwebtoken');
const app = express();
app.get('/', (req, res) => {
throw new jwt.JsonWebTokenError('this is sample error');
});
const newErrorMap = new Map([
['JsonWebTokenError', createErrors.BadRequest],
['ValidationError', createErrors.BadRequest],
]);
app.use((err, req, res, next) => {
const newError = newErrorMap.get(err.name);
if(newError) {
next(new newError(err.message));
} else {
next(err);
}
});
app.listen(3000, () => { console.log('listen'); });