エラーハンドリング
想定していない失敗は必ず起きる
第5章の入力検証で、おかしな入力は 400 で弾けるようになりました。ですが世の中には、こちらが想定していなかった失敗があります。
ファイルが壊れていた。データベースが一瞬つながらなかった。書いたコードに勘違いがあった。
こうした失敗は、エラーとして投げられます。そのまま放っておくと、応答が返らないままリクエストが終わります。受け取る側から見ると、ただ固まったように見えます。
エラー用のミドルウェア
Express には、エラーを受け止める専用のミドルウェアがあります。
JavaScript
app.use((err, req, res, next) => {
res.status(500).json({ error: "サーバーで問題が起きました" });
});引数が 4 つあるのが目印です。前のレッスンのミドルウェアは 3 つでした。この 1 つの違いで、Express はエラー用だと見分けます。省略できません。
置く場所は一番下
エラー用のミドルウェアは、すべてのルートより後に書きます。
JavaScript
app.get("/items", ...);
app.post("/items", ...);
app.use((err, req, res, next) => { ... }); // 最後リクエストは上から順に通るので、途中に置くとその下のルートのエラーを拾えません。最後の受け皿として置きます。
中身をそのまま返さない
エラーには、内部の情報が入っていることがあります。
JavaScript
res.status(500).json({ error: err.message }); // 避けたいファイルの場所やデータベースの構造が、そのまま外に出るかもしれません。攻撃の手がかりになります。
外には決まった文言を返し、詳しい中身は自分の記録に残します。
JavaScript
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: "サーバーで問題が起きました" });
});非同期の失敗は自分で渡す
async を付けた関数の中で起きたエラーは、自動では拾われません。
JavaScript
app.get("/items", async (req, res, next) => {
try {
const items = await readItems();
res.json(items);
} catch (error) {
next(error);
}
});next にエラーを渡して呼ぶと、エラー用のミドルウェアへ回されます。引数なしの next() が「次へ進む」なのに対し、next(error) は「失敗したので受け皿へ」という意味になります。
手を動かす
演習では、落ちないAPIを作ります。エラーを受け止める最後の受け皿を用意してください。
要件
- すべてのルートより後に、引数 4 つのエラー用ミドルウェアを置く
- 500 で
{"error":"サーバーで問題が起きました"}を返し、エラーの中身は外に出さない /async-boomはtryとcatchで受けてnext(error)へ渡す
入出力例
request("GET", "/items") → "200 [{"id":1,"name":"レザースニーカー"}]"
request("GET", "/boom") → "500 {"error":"サーバーで問題が起きました"}"
request("GET", "/async-boom") → "500 {"error":"サーバーで問題が起きました"}"ヒント
編集 ゆめさく編集部