かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Express な API でエラーハンドリングするよ

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', await (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', await (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', await (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 が無い時は通常のエラーオブジェクトが使われ、 errorundefined になる。

これで Express を API にしていい感じにエラーハンドリングができるようになりました!
₍ ᐢ. ̫ .ᐢ ₎ ヤッタネ
Nest.js 使えるようになれよ…って話でもあるケド


[参考]

自転車えくすぷれす

自転車えくすぷれす

  • 発売日: 2014/04/01
  • メディア: MP3 ダウンロード
さよならポニーテールを聞こう!