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として外に公開するコードでは「このエラーは誰のせいなのか」を表現しないといけない。そのことを、レビューで初めて知りました。
