プログラミング実践

【NestJS】「HTTPステータス500を返していたら400を返すべき」と指摘された話・API設計の基礎を知った

記事内に商品プロモーションを含む場合があります

NestJSでAPIを作っていたとき、エラーが発生したら全部 { status: 500, message: error.message } で返していました。

動いてはいた。エラーが起きたら500を返して、フロント側でメッセージを表示する。何が悪いのかわかっていませんでした。

レビューで「それ、400を返すべきケースじゃないですか?」と指摘されるまで、HTTPステータスコードの使い分けを考えたことがありませんでした。同じように全部500で返している人の参考になれば幸いです。

前提:どんなコードを書いていたか

チーム開発でNestJSのAPIを実装していたときの話です。データを登録するサービスの中で、try-catchしてエラーが起きたらこう返していました。

// サービス層
async createOrder(dto: CreateOrderDto) {
  try {
    const result = await this.orderRepository.create(dto);
    return { status: 200, data: result };
  } catch (error) {
    return { status: 500, message: error.message };
  }
}

エラーの種類に関係なく、全部500。リクエストのバリデーションエラーでも、データが見つからなくても、認証エラーでも、とにかく500を返していました。

Pythonでスクリプトを書いていたときは、エラーが起きたら print(error) して終わりだったので、500を返すだけでも「ちゃんとエラーハンドリングしている」と思っていました。

指摘されたこと

レビューで指摘されたのは、こういう内容でした。

「リクエストの内容がおかしい場合は400(Bad Request)、データが見つからない場合は404(Not Found)を返すべきです。500はサーバー側の予期しないエラーのときだけです。」

言われてみれば当たり前のことでした。でも当時は、既存のコードでも同じように500を返している箇所がいくつもあって、自分はそれを参考にして書いていただけでした。前任者も周りもそう書いていたから、深く考えずに真似していた。なのに、新しく入った自分にだけレビューで厳しく指摘がきてキレそうだったのを覚えています。

ただ、結果的にはそのおかげでHTTPステータスコードの使い分けをちゃんと学ぶきっかけになりました。

HTTPステータスコードの基本

指摘を受けて調べてみると、HTTPステータスコードには明確な分類がありました。

よく使うステータスコード

コード 意味 どんなとき
200 OK 正常に処理が完了した
201 Created データの新規作成が成功した
400 Bad Request リクエストの内容がおかしい(バリデーションエラー等)
401 Unauthorized 認証されていない(ログインしていない等)
403 Forbidden 認証済みだがアクセス権限がない
404 Not Found 指定されたリソースが見つからない
500 Internal Server Error サーバー側の予期しないエラー

ポイントは4xx系と5xx系の違いです。

  • 4xx:クライアント(リクエストを送った側)が悪い。「送り方を直してください」
  • 5xx:サーバー(受け取った側)が悪い。「こちらの問題です、すみません」

全部500で返すということは、「全部サーバーのせいです」と言っていたようなものでした。リクエストの内容が間違っていても「サーバーが悪い」と返していた。これではフロント側もどう対処すればいいかわかりません。

NestJSでの正しい書き方

NestJSには、ステータスコードごとの例外クラスが用意されています。これを使うと、適切なステータスコードが自動で返されます。

import {
  BadRequestException,
  NotFoundException,
  UnauthorizedException,
  InternalServerErrorException,
} from '@nestjs/common';

修正後のコードはこうなりました。

// コントローラー層
@Post()
async createOrder(@Body() dto: CreateOrderDto) {
  const result = await this.orderService.createOrder(dto);

  switch (result.status) {
    case 400:
      throw new BadRequestException(result.message);
    case 404:
      throw new NotFoundException(result.message);
    case 401:
      throw new UnauthorizedException(result.message);
    default:
      throw new InternalServerErrorException(result.message);
  }
}

throw new BadRequestException('注文データが不正です') と書くだけで、レスポンスは自動的にこうなります。

{
  "statusCode": 400,
  "message": "注文データが不正です",
  "error": "Bad Request"
}

{ status: 500, message: error.message } を手動で返していたころに比べると、フロント側が「これはユーザーの入力ミスだ」と判断できるようになりました。

まとめ

振り返ると、全部500で返していたのは「エラーの原因が誰にあるのか」を考えたことがなかったからでした。

  • 4xx系は「リクエストを送った側の問題」、5xx系は「サーバー側の問題」
  • NestJSでは BadRequestException / NotFoundException などの例外クラスを使えば、適切なステータスコードが自動で返される
  • ステータスコードを正しく返すことで、フロント側がエラーの種類に応じた表示を出せるようになる
  • 500は「本当に予期しないエラー」のときだけ

Pythonでスクリプトを書いていたころは、エラーが起きたら print して終わりでした。APIとして外に公開するコードでは「このエラーは誰のせいなのか」を表現しないといけない。そのことを、レビューで初めて知りました。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA