目次
- 環境
- APIのエラーハンドリング実装
環境
フロントエンドアプリケーション
- Next v13.4.19
- react v18.2.0
- TypeScript v5.2.2
API
- Node v18.17.1
- Express v4.18.2
- TypeScript v5.2.2
DB
- Prisma v5.2.0
- mysql v8.0
ローカル環境
DockerAPIのCRUD実装
Prisma固有のエラー
公式ドキュメントを見ると、Prisma固有のエラーは以下のように大別できる。
- PrismaClientKnownRequestError
- PrismaClientValidationError
- PrismaClientRustPanicError
- PrismaClientInitializationError
- PrismaClientUnknownRequestError
さらにこのうち、PrismaClientKnownRequestErrorはcodeというプロパティを持っており、このコードによりかなり細分化されている(例としては、メールアドレスのユニーク制約違反など)
PrismaClientKnownRequestError
Prismaクライアントにより、Code別であらかじめ分類されている既知のエラー
PrismaClientValidationError
新規レコードを作成する際に必須のキーが存在していない場合に生じるエラー
PrismaClientRustPanicError
基盤となるエンジンがクラッシュし、ゼロ以外の終了コードで終了した場合に生じるエラー
PrismaClientInitializationError
データベースへの接続に失敗するなどの場合に生じるエラー
PrismaClientUnknownRequestError
上記に分類されないエラー
APIのエラーハンドリング実装
CRUDのうち、新規作成でエラーハンドリングを実装する。新規作成以外も基本的に同じように実装できるか、これよりも簡単なはずなので本記事では時間の関係上割愛する。
方針
名前、メールアドレスいずれも必須としており、かつメールアドレスにはユニーク制約がある。そのため、新規作成時にエラーが生じた場合は
- PrismaClientKnownRequestErrorまたはPrismaClientValidationErrorであれば、対応するエラーメッセージをステータスコード4XX系で返却
- 上記以外であれば、システムエラーである旨のエラーメッセージをステータスコード500で返却
とする。(※プロダクトコードであれば、もう少し深く設計/検討した方がいいが、チュートリアルなのでこれくらいでいいと思う)
実装の全体像
あくまでサンプルだが以下のようになった
import express, { Request, Response } from "express";
import { PrismaClient, User } from "@prisma/client";
import { CreateUserUseCase } from "../useCase/CreateUserUseCase";
import {
PrismaClientKnownRequestError,
PrismaClientValidationError,
} from "@prisma/client/runtime/library";
const router = express.Router();
/**
* ユーザー新規作成
*/
router.post("", async (req: Request, res: Response) => {
const { name, email } = parseReqBody(req);
try {
const useCase = new CreateUserUseCase();
const user = await useCase.create(name, email);
return res.status(200).json({
user,
});
} catch (e: unknown) {
console.log(e);
// System error & PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientUnknownRequestErrorで使用する
let statusCode = 500;
let errorMessages = ["System error"];
/**
* 予想されるエラー
*
* - 1.PrismaClientKnownRequestError
* - instanceofとerror.codeを分岐して対応する
* - 2.PrismaClientValidationError
* - instanceofで対応
* - 上記以外は共通のシステムエラーで対応(500)
*/
if (e instanceof PrismaClientKnownRequestError) {
if (e.code === "P2002") {
statusCode = 400;
errorMessages = ["The email address is already in use"];
}
}
if (e instanceof PrismaClientValidationError) {
const regex = /Argument.*?missing./g;
const matches = e.message.match(regex);
statusCode = 400;
if (matches) {
errorMessages = matches;
}
}
return res.status(statusCode).json({
statusCode,
error: errorMessages,
});
}
});
ポイント
上記の通り、共通のサーバー側でのシステムエラー、データベースに接続できないなど(PrismaClientInitializationErrorなど)はブラウザには500でエラーメッセージを返却し、(実際は)管理者へのエラーの通知などを行うため、最初に変数として用意しておく
catch (e: unknown) {
// System error & PrismaClientRustPanicError, PrismaClientInitializationError, PrismaClientUnknownRequestError
let statusCode = 500;
let errorMessages = ["System error"];
次に、Prisma既知のエラーでcodeプロパティを持っている場合(PrismaClientKnownRequestError)であれば、instanceofで判定し、続いてe.codeと一致するかで分岐。今回はユニーク制約のコードがP2002なのでこれで判定する。
if (e instanceof PrismaClientKnownRequestError) {
if (e.code === "P2002") {
statusCode = 400;
errorMessages = ["The email address is already in use"];
}
}
続いてnameとemailがきちんとブラウザから送信されているかをチェックする。
if (e instanceof PrismaClientValidationError) {
const regex = /Argument.*?missing./g;
const matches = e.message.match(regex);
statusCode = 400;
if (matches) {
errorMessages = matches;
}
}
上記で正規表現を使っているのは、以下のようなエラーメッセージから、どのパラメーターが不足しているのかというのを取り出すため。(ここは少し使いずらさがある・・・・)ステータスコード400で取り出したエラーメッセージを返却する
Invalid `prisma.user.create()` invocation:
{
data: {
name: "",
+ email: String
}
}
Argument `email` is missing.
これ以外のエラーについては、ステータスコード500で共通のシステムエラーメッセージを表示する。
ちなみに、500エラーを試したければ、DBコンテナを停止してAPIにリクエストを送信する(PrismaClientInitializationError)か、tryブロックでthow new ErrorとしてあげればOK。
return res.status(statusCode).json({
statusCode,
error: errorMessages,
});
まとめ
いかがでしたでしょうか。本記事は、ExpressとPrismaでAPIをサクッと作るシリーズのPart4です。Part4では主にPrismaでのエラーの分類やAPIのエラーハンドリングを実装しました。Part5では主にPrismaでのリレーションや高度なカラム設定を行います