#アジェンダ
標題通り、Expressのエラーハンドリングの実施方法。githubに挙がっていたソースコードを元にエラーハンドリングを実施
#ソースコード
ディレクトリ構成
src
├── error-handler
│ ├── error-code.ts
│ ├── error-exception.ts
│ ├── error-handler.ts
│ └── error-model.ts
└── server.ts
error-code.ts
import express, { NextFunction } from 'express';
import { Request, Response } from 'express';
import { ErrorCode } from './error-handler/error-code';
import { ErrorException } from './error-handler/error-exception';
import { errorHandler } from './error-handler/error-handler';
const app = express();
app.get('/', (req: Request, res: Response) => {
res.send('Application works!');
});
app.get('/throw-unauthenticated', (req: Request, res: Response, next: NextFunction) => {
throw new ErrorException(ErrorCode.Unauthenticated);
// or
// next(new ErrorException(ErrorCode.Unauthenticated))
});
app.get('/throw-maximum-allowed-grade', (req: Request, res: Response, next: NextFunction) => {
throw new ErrorException(ErrorCode.MaximumAllowedGrade, { grade: Math.random() });
// or
// next(new ErrorException(ErrorCode.MaximumAllowedGrade, { grade: Math.random() }))
});
app.get('/throw-unknown-error', (req: Request, res: Response, next: NextFunction) => {
const num: any = null;
// Node.js will throw an error because there is no length property inside num variable
console.log(num.length);
});
const someOtherFunction = () => {
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
reject(new ErrorException(ErrorCode.AsyncError));
}, 1000);
});
return myPromise;
};
app.get('/throw-async-await-error', async (req: Request, res: Response, next: NextFunction) => {
// express 4
try {
await someOtherFunction();
} catch (err) {
next(err);
// next line will not work as expected
// throw err
}
// express 5
// await someOtherFunction();
});
app.use(errorHandler); // registration of handler
app.listen(3000, () => {
console.log('Application started on port 3000!');
});
error-code.ts
export class ErrorCode {
public static readonly Unauthenticated = 'Unauthenticated';
public static readonly NotFound = 'NotFound';
public static readonly MaximumAllowedGrade = 'MaximumAllowedGrade';
public static readonly AsyncError = 'AsyncError';
public static readonly UnknownError = 'UnknownError';
}
error-exception.ts
import { ErrorCode } from './error-code';
export class ErrorException extends Error {
public status: number = null;
public metaData: any = null;
constructor(code: string = ErrorCode.UnknownError, metaData: any = null) {
super(code);
Object.setPrototypeOf(this, new.target.prototype);
this.name = code;
this.status = 500;
this.metaData = metaData;
switch (code) {
case ErrorCode.Unauthenticated:
this.status = 401;
break;
case ErrorCode.MaximumAllowedGrade:
this.status = 400;
break;
case ErrorCode.AsyncError:
this.status = 400;
break;
case ErrorCode.NotFound:
this.status = 404;
break;
default:
this.status = 500;
break;
}
}
}
error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { ErrorCode } from './error-code';
import { ErrorException } from './error-exception';
import { ErrorModel } from './error-model';
export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
console.log('Error handling middleware called.');
console.log('Path:', req.path);
console.error('Error occured:', err);
if (err instanceof ErrorException) {
console.log('Error is known.');
res.status(err.status).send(err);
} else {
// For unhandled errors.
res.status(500).send({ code: ErrorCode.UnknownError, status: 500 } as ErrorModel);
}
};
error-model.ts
export class ErrorModel {
/**
* Unique error code which identifies the error.
*/
public code: string;
/**
* Status code of the error.
*/
public status: number;
/**
* Any additional data that is required for translation.
*/
public metaData?: any;
}
#所感
エラーハンドリングをライブラリで管理できるのは便利。ただ、改善ポイントが複数あるので、個別カスタマイズか、別のライブラリを探してみるのもアリかと。
■改善箇所。
・HTTPステータスとcodeが一致しているので、冗長。
・障害発生時、一意に確認できるコードを付与すべき({errorCode:'ERR001'})
・エラーエクセプションのswitch文が冗長。エラーコードとステータスは一緒に定義した方が管理しやすい。