ウェブエンジニア問題集
第6章

Fastifyのエラーハンドリング — 4xxと500を整理して返す

4
この章の目次開く

APIでは、成功よりも失敗の扱いで設計の差が出ます。入力値が間違っている、データが存在しない、権限がない、DBが落ちている。これらをすべて同じ 500 にすると、クライアントも運用者も原因を判断できません。

Fastifyでは、handlerから throw したエラーやschema validationのエラーを、共通のエラーハンドラで整理できます。

まずステータスを分ける

状況ステータス
入力値が不正400title が空
認証が必要401トークンがない
権限がない403他人のTodoを編集しようとした
見つからない404指定IDのTodoがない
競合409同じ名前が既にある
サーバー側の想定外500DB接続失敗、バグ
予期できる失敗は4xx、予期しない失敗は500。この線引きを最初に決めると、エラー処理が整理しやすくなります。

setErrorHandler() の構文

構文: fastify.setErrorHandler(handler)

引数説明
handlerfunction(error, request, reply) を受け取るエラー処理関数

戻り値: Fastifyインスタンス。設定処理として使います。

fastify.setErrorHandler((error, request, reply) => {
  request.log.error(error);
 
  return reply.code(500).send({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'サーバーエラーが発生しました',
    },
  });
});
js

バリデーションエラーを整える

schema validationで失敗したエラーには、Fastifyが validation などの情報を付けます。これを使って、入力エラーだけ 400 として返します。

fastify.setErrorHandler((error, request, reply) => {
  if (error.validation) {
    return reply.code(400).send({
      error: {
        code: 'VALIDATION_ERROR',
        message: '入力値が正しくありません',
        details: error.validation,
      },
    });
  }
 
  request.log.error(error);
 
  return reply.code(500).send({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'サーバーエラーが発生しました',
    },
  });
});
js

業務エラーを作る

存在しないTodoを指定された場合は、handler内で404を返しても構いません。ただ、同じエラーが複数箇所に出るなら、専用のエラークラスにすると整理しやすくなります。

class NotFoundError extends Error {
  constructor(message = 'Not Found') {
    super(message);
    this.name = 'NotFoundError';
    this.statusCode = 404;
    this.code = 'NOT_FOUND';
  }
}
js
fastify.get('/todos/:id', async (request) => {
  const todo = todos.find((item) => item.id === request.params.id);
 
  if (!todo) {
    throw new NotFoundError('Todoが見つかりません');
  }
 
  return todo;
});
js

エラーハンドラ側で statusCodecode を見ます。

fastify.setErrorHandler((error, request, reply) => {
  if (error.statusCode && error.statusCode < 500) {
    return reply.code(error.statusCode).send({
      error: {
        code: error.code ?? 'BAD_REQUEST',
        message: error.message,
      },
    });
  }
 
  request.log.error(error);
 
  return reply.code(500).send({
    error: {
      code: 'INTERNAL_ERROR',
      message: 'サーバーエラーが発生しました',
    },
  });
});
js

404ハンドラ

存在しないURLにアクセスされた場合は、setNotFoundHandler() で形式を揃えます。

fastify.setNotFoundHandler((request, reply) => {
  return reply.code(404).send({
    error: {
      code: 'ROUTE_NOT_FOUND',
      message: '存在しないURLです',
    },
  });
});
js
学習者学習者

404ってエラーハンドラだけで拾えないんですか?

先生先生

ルートが見つからない状態は、handler内でthrowされた例外とは別物です。Fastifyではnot found専用のhandlerで整えるのが分かりやすいです。

参考リンク

次章では、ルートや設定を機能単位に分けるためのプラグインを学びます。

Node.jsクイズに挑戦する例外、HTTPステータス、JSONレスポンスの知識をクイズで確認しよう