😺

【Express】APIの例外処理実装と解説

2023/08/19に公開

expressを利用した例外処理について解説します。

例外処理

例外処理とは

実行プログラム内でのエラー発生時に、エラーの内容によって実行される処理の事です。
下記に、例外処理の必要性を2つ挙げます。

  • エラー時の対応を定義しておかなければサーバダウンなどのリスクが発生する
  • 開発側からユーザーに与える情報を定義することで、余計な情報を与えずセキュリティ向上に繋がる

実装

今回は例外発生時に共通のエラークラスを呼び出し、共通エラー処理の関数に飛ばしてレスポンスを返す流れで実装します。
様々なケースが存在しますが、今回はシンプルな実装を意識しています。

CustomException

まず、独自例外と共通エラー処理を定義します。
CostomExceptionクラスが例外発生時にthrowされ、statusCode、message、logLevelの3つを引数として取ってconstructorに渡しています。
その後、CustomExceptionクラスから生成されたerrorオブジェクトとして共通エラー処理で引き取られる形になります。

  • statusCode ~ 例外に対応するステータスコード
  • message ~ エラーの内容を伝えるメッセージ
  • logLevel ~ info, warn, errorなどで例外の種類を定義する。(Zabbixなどで検知する例外を限定する為)
export class CustomException extends Error {
  statusCode: number;
  logLevel: string;

  constructor(statusCode: number, message: string, logLevel: string) {
    super(message);
    this.statusCode = statusCode;
    this.logLevel = logLevel;
    Object.setPrototypeOf(this, CustomException.prototype);
  }
}

共通エラー処理

例外発生時はcatchでerrorHandler関数を呼び出してCuatomExceptionクラスのインスタンスを処理します。。
CustomExceptionクラスで生成したerrorオブジェクトを使用し、jsonレスポンスとして返却しています。
どの例外処理にも該当しない例外が発生した場合は、500のinternal server errorとして返却するようにしています。

import { Response } from "express";

export const errorHandler = (error: any, res: Response) => {
  error.statusCode = error.statusCode || 500;
  error.message = error.message || null;
  error.logLevel = error.logLevel || null;

  if (error instanceof CustomException) {
    return res.status(error.statusCode).json({
      statusCode: error.statusCode,
      message: error.message,
      logLevel: error.logLevel,
    });
  } else {
    res.status(500).json({
      message: "internal server error",
      logLevel: "error",
    });
  }
};

controller

エラークラスをthrowするcontrollerを定義します。
ifで例外を捕捉し、throw new CustomExceptionで独自エラークラスをインスタンス化、必要な引数を定義しています。
例外発生で処理はcatchに流れ、next(errorHandler())でレスポンスを返却する共通エラークラスに飛ばしています。

export class BoardController {
  async allBoard(_req: Request, res: Response): Promise<void> {
    const boards = await getBoards();
    res.status(200).json({
      message: "board get all success",
      boards,
    });
  }

  async postBoard(
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> {
    const { title, content, boardImage, userId } = req.body;
    try {
      const board = await createBoard(title, content, boardImage, userId);

      if (!board)
        throw new CustomException(400, "this board does not create", "info");

      res.status(201).json({
        message: "this board create is success",
        board,
      });
    } catch (error: any) {
      return next(errorHandler(error, res));
    }
  }

  async showBoard(
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> {
    try {
      const id = parseInt(req.params.id);

      const board = await getBoard(id);

      if (!board)
        throw new CustomException(404, "this board does not get", "info");

      res.status(200).json({
        message: "this board get is success",
        board,
      });
    } catch (error: any) {
      return next(errorHandler(error, res));
    }
  }
}

{
  "statusCode": 400,
  "message": "this board does not create",
  "logLevel": "info"
}

Discussion