エラーハンドリング
エラーは必ず起きるもの。適切に処理してユーザー体験を損なわないようにしましょう。
FastAPIのエラー処理
HTTPException
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/posts/{post_id}")
def get_post(post_id: int):
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Post not found"
)
return post
@app.post("/posts")
def create_post(post: PostCreate, current_user: User = Depends(get_current_user)):
if not current_user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"}
)
# 作成処理...
return new_post
カスタム例外ハンドラー
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
# カスタム例外
class PostNotFoundError(Exception):
def __init__(self, post_id: int):
self.post_id = post_id
# 例外ハンドラー登録
@app.exception_handler(PostNotFoundError)
async def post_not_found_handler(request: Request, exc: PostNotFoundError):
return JSONResponse(
status_code=404,
content={
"error": "PostNotFound",
"message": f"Post with id {exc.post_id} not found",
}
)
# バリデーションエラーのカスタマイズ
from fastapi.exceptions import RequestValidationError
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=422,
content={
"error": "ValidationError",
"details": exc.errors()
}
)
Next.jsのエラー処理
error.tsx(エラー境界)
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-bold text-red-600 mb-4">
問題が発生しました
</h2>
<p className="text-gray-600 mb-6">
{error.message || "予期しないエラーが発生しました"}
</p>
<button
onClick={reset}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
>
もう一度試す
</button>
</div>
</div>
);
}
not-found.tsx(404ページ)
import Link from "next/link";
export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-300 mb-4">404</h1>
<h2 className="text-2xl font-bold mb-4">ページが見つかりません</h2>
<p className="text-gray-600 mb-6">
お探しのページは存在しないか、移動した可能性があります。
</p>
<Link
href="/"
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700"
>
トップページへ
</Link>
</div>
</div>
);
}
notFound()の呼び出し
import { notFound } from "next/navigation";
async function getPost(id: string) {
const res = await fetch(`${API_URL}/posts/${id}`);
if (res.status === 404) {
notFound(); // not-found.tsx を表示
}
if (!res.ok) {
throw new Error("Failed to fetch post"); // error.tsx を表示
}
return res.json();
}
export default async function PostPage({ params }: { params: { id: string } }) {
const post = await getPost(params.id);
return <div>{post.title}</div>;
}
API呼び出しのエラー処理
"use client";
import { useState } from "react";
export function CreatePostForm() {
const [error, setError] = useState<string | null>(null);
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setFieldErrors({});
try {
const res = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(formData),
});
if (!res.ok) {
const data = await res.json();
// バリデーションエラー
if (res.status === 422) {
const errors: Record<string, string> = {};
data.details?.forEach((err: any) => {
const field = err.loc[err.loc.length - 1];
errors[field] = err.msg;
});
setFieldErrors(errors);
return;
}
// 認証エラー
if (res.status === 401) {
setError("ログインが必要です");
return;
}
// その他のエラー
setError(data.message || "エラーが発生しました");
return;
}
// 成功
alert("投稿しました!");
} catch (err) {
// ネットワークエラーなど
setError("通信エラーが発生しました。インターネット接続を確認してください。");
}
};
return (
<form onSubmit={handleSubmit}>
{error && (
<div className="bg-red-100 text-red-700 p-3 rounded mb-4">
{error}
</div>
)}
<div>
<input name="title" />
{fieldErrors.title && (
<p className="text-red-500 text-sm">{fieldErrors.title}</p>
)}
</div>
<button type="submit">投稿</button>
</form>
);
}
ユーザーフレンドリーなエラー表示
悪い例
- ✗ "Error: ECONNREFUSED"
- ✗ "500 Internal Server Error"
- ✗ "undefined is not a function"
技術的な内容は見せない
良い例
- ○ "通信エラーが発生しました"
- ○ "入力内容に誤りがあります"
- ○ "しばらく経ってから再度お試しください"
次のアクションを示す
まとめ
- ✓ FastAPIはHTTPExceptionで明示的にエラーを返す
- ✓ Next.jsはerror.tsx, not-found.tsxでエラー画面を設定
- ✓ API呼び出しはtry-catchで囲む
- ✓ ユーザーには分かりやすいメッセージを表示