TypeScript な Express でAPI作ってエラーを返す処理を都度書いているとめんどいので、エラーハンドリングして返すようにしたメモ
エラーハンドリングのミドルウェアはルーティングより後に書く
上から順番にルーティングにマッチしていくか見ていって、該当するルーティングが無ければエラーハンドリングするミドルウェアに処理が流れてくるイメージ
import express, { HttpException, Request, Response, NextFunction } from 'express'; import apiRouter from './api/controller' const app = express(); const router = express.Router(); // Routing app.get('/api/user', async (req: Request, res: Response, next: NextFunction): Promise<data> => { try { const data = await something(….); return data; } catch (err) { return next(err); } }); // error handling export default function errorHandler( err: HttpException, req: Request, res: Response, ): void { res.status(500).send('error message'); }
ルーティング内で next(new Error())
されるとエラーハンドリングに処理が流れる
エラーのステータスを変えたりエラー時にデータを返したい
Nest.js みたいにエラーのステータスごとの関数を使ってエラーが返せるようになると楽そう。
一緒にエラー時にもデータを返したい!
HttpException
クラスを拡張してエラーを返す関数を作成すれば OK
// errorException.ts import { Request, Response } from 'express'; class HttpException extends Error { statusCode?: number; message: string; constructor(statusCode: number, message: string) { super(message); this.statusCode = statusCode || 500; this.message = message; } } export const badRequestException = (message = '400 Bad Request'): HttpException => { return new HttpException(400, message); }; export const forbiddenException = (message = '403 Forbidden'): HttpException => { return new HttpException(403, message); }; // Error Handler Middleware export default function errorHandler( err: HttpException, req: Request, res: Response, ): void { res.status(err.statusCode || 500).send(err.message); }
// app.ts import express, { HttpException, Request, Response, NextFunction } from 'express'; import apiRouter from './api/controller' import errorHandler, { badRequestException } fromn './errorException'; const app = express(); const router = express.Router(); // Routing app.get('/api/user', async (req: Request, res: Response, next: NextFunction): Promise<data> => { try { const data = await something(….); return data; } catch (err) { return next( badRequestException('ジョニー別府!') ); } }); app.use(errorHandler);
ブラウザで直接アクセスするとエラーメッセージが表示される。
axios でこの API を叩いている場合、エラーメッセージは error.response.data
に格納されるので API で使用する場合は次のようにエラーメッセージを取得する
try { // ... } catch (err) { const errorMessage = err?.response?.data || err.message; // => "ジョニー別府!" }
これでデフォルトのエラーオブジェクトの代わりにステータスコードを変更してカスタムメッセージを付けたエラーハンドリングができるようになしました!
エラーに独自のオブジェクトを追加して返す
API として使う場合、メッセージ以外にも情報を返したいケースがあります。
その場合はフロントで使いたい情報のオブジェクトを返すようにすればOK
// errorException.ts import { Request, Response } from 'express'; type errorData = { [key: string]: any; }; class HttpException extends Error { statusCode?: number; message: string; data: errorData; constructor(statusCode: number, message: string, data?: errorData) { super(message); this.statusCode = statusCode || 500; this.message = message; this.data = data ? { ...data } : {}; } } export const badRequestException = (message = '400 Bad Request', data?: errorData,): HttpException => { return new HttpException(400, message, data); }; export const forbiddenException = (message = '403 Forbidden', data?: errorData,): HttpException => { return new HttpException(403, message, data); }; // Error Handler Middleware export default function errorHandler( err: HttpException, req: Request, res: Response, ): void { // 直接 メッセージだけを入れられないので error {} 内にメッセージ・データを格納する res.status(err.statusCode || 500).send({ message: err.message, error: { ...err.data, }, }); }
// Routing app.get('/api/user', async (req: Request, res: Response, next: NextFunction): Promise<data> => { try { const data = await something(….); return data; } catch (err) { return next( badRequestException('ジョニー別府!', { type: "Not idol, Great teacher!" }) ); } });
フロント
try { // ... } catch (err) { const { message, error } = err?.response?.data || err; // => "ジョニー別府!", { type: "Not idol, Great teacher!" } }
err.response.data
が無い時は通常のエラーオブジェクトが使われ、 error
は undefined
になる。
これで Express を API にしていい感じにエラーハンドリングができるようになりました!
₍ ᐢ. ̫ .ᐢ ₎ ヤッタネ
Nest.js 使えるようになれよ…って話でもあるケド
[参考]
さよならポニーテールを聞こう!