1: 画面とAPIの連携
このステップで学ぶこと
Section titled “このステップで学ぶこと”このステップでは、Next.jsで作成したフロントエンドとFastAPIで作成したバックエンドを連携させる方法を学びます。
このステップの手順を実施するには Next.js チュートリアルと FastAPI チュートリアルが完了している必要があります。
1. バックエンド
Section titled “1. バックエンド”CORSの設定
Section titled “CORSの設定”todo-api/app/main.py
を編集して CORS の設定を追加します:
from fastapi import FastAPIfrom fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# CORSの設定app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # フロントエンドのURL allow_credentials=True, allow_methods=["*"], # すべてのHTTPメソッドを許可 allow_headers=["*"], # すべてのヘッダーを許可)
# 既存のエンドポイントはそのまま使用
💡 CORSとは?
CORS(Cross-Origin Resource Sharing)は、異なるWebサイト間での通信を許可するかどうかを制御する仕組みです。 フロントエンド(
http://localhost:3000
)からバックエンド(http://localhost:8000
)のAPIを呼び出す場合、 バックエンド側で「http://localhost:3000
からのアクセスを許可する」と設定する必要があります。
2. フロントエンド
Section titled “2. フロントエンド”APIクライアントの作成
Section titled “APIクライアントの作成”src/app/api/todos.js
を作成し、以下のコードを作成します:
const API_BASE_URL = 'http://localhost:8000';
// Todo一覧の取得export async function getTodos() { const response = await fetch(`${API_BASE_URL}/todos/`); if (!response.ok) { throw new Error('Todoの取得に失敗しました'); } return response.json();}
// 新規Todoの作成export async function createTodo(title) { const response = await fetch(`${API_BASE_URL}/todos/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title }), }); if (!response.ok) { throw new Error('Todoの作成に失敗しました'); } return response.json();}
// Todoの完了状態の更新export async function completeTodo(id) { const response = await fetch(`${API_BASE_URL}/todos/${id}/complete`, { method: 'PATCH', }); if (!response.ok) { throw new Error('Todoの更新に失敗しました'); } return response.json();}
// Todoの削除export async function deleteTodo(id) { const response = await fetch(`${API_BASE_URL}/todos/${id}`, { method: 'DELETE', }); if (!response.ok) { throw new Error('Todoの削除に失敗しました'); } return response.json();}
Todoコンポーネントの変更
Section titled “Todoコンポーネントの変更”src/app/todos/page.js
を以下のように更新します:
'use client';import { useState, useEffect } from 'react';import { getTodos, createTodo, completeTodo, deleteTodo } from '../api/todos';import style from './page.module.css';
export default function Todos() { const [todos, setTodos] = useState([]); const [newTodo, setNewTodo] = useState('');
// Todo一覧の取得 useEffect(() => { const fetchTodos = async () => { try { const data = await getTodos(); setTodos(data); } catch (error) { console.error('Todoの取得に失敗しました:', error); } }; fetchTodos(); }, []);
// Todo追加処理 const handleSubmit = async (e) => { e.preventDefault(); if (newTodo.trim() === '') return;
try { const newTodoData = await createTodo(newTodo); setTodos([...todos, newTodoData]); setNewTodo(''); } catch (error) { console.error('Todoの作成に失敗しました:', error); } };
// Todoの完了状態を更新する処理 const handleToggleComplete = async (todoId) => { try { const updatedTodo = await completeTodo(todoId); setTodos(todos.map(todo => todo.id === todoId ? updatedTodo : todo )); } catch (error) { console.error('Todoの更新に失敗しました:', error); } };
// Todoを削除する処理 const handleDelete = async (todoId) => { if (window.confirm('このTodoを削除しますか?')) { try { await deleteTodo(todoId); setTodos(todos.filter(todo => todo.id !== todoId)); } catch (error) { console.error('Todoの削除に失敗しました:', error); } } };
return ( <section className={style.container}> <h1>Todoリスト</h1>
<form onSubmit={handleSubmit} className={style.form}> <input type="text" value={newTodo} onChange={(e) => setNewTodo(e.target.value)} placeholder="新しいTodoを入力" className={style.input} /> <button type="submit" className={style.button}> 追加 </button> </form>
<div className={style.checkbox}> {todos.map((todo) => ( <div key={todo.id} className={style.todoItem}> <label className={todo.completed ? style.completed : ''}> <input type="checkbox" checked={todo.completed} onChange={() => handleToggleComplete(todo.id)} /> {todo.title} </label> <button onClick={() => handleDelete(todo.id)}>削除</button> </div> ))} </div> </section> );}
3. 動作確認
Section titled “3. 動作確認”サーバーの起動
Section titled “サーバーの起動”-
バックエンドサーバーの起動:
Terminal window cd todo-apisource venv/bin/activate # Windows: venv\Scripts\activateuvicorn app.main:app --reloadサーバーは
http://localhost:8000
で起動します。 -
フロントエンドサーバーの起動:
Terminal window cd todo-managernpm run devサーバーは
http://localhost:3000
で起動します。
基本的な機能の確認
Section titled “基本的な機能の確認”ブラウザで http://localhost:3000/todos
を開き、以下の機能を順番にテストします:
-
Todoの作成
- テキストボックスに「買い物に行く」と入力
- 「追加」ボタンをクリック
- 新しいTodoがリストに表示されることを確認
- テキストボックスが空になることを確認
-
Todoの完了状態の確認
- 作成したTodoのチェックボックスをクリック
- Todoのテキストに取り消し線が表示されることを確認
- もう一度チェックボックスをクリックしても、取り消し線は消えないことを確認(=一度完了にしたら未完了には戻せない)
-
Todoの削除
- Todoの「削除」ボタンをクリック
- 確認ダイアログで「OK」をクリック
- Todoがリストから消えることを確認
エラー処理の確認
Section titled “エラー処理の確認”-
バックエンドサーバーを停止
- ターミナルで
Ctrl+C
を押してサーバーを停止 - フロントエンドでTodoの追加・完了・削除を試みる
- エラーメッセージがコンソールに表示されることを確認
- ターミナルで
-
バックエンドサーバーを再起動
- サーバーを再起動
- フロントエンドの操作が正常に動作することを確認
- Todoリストが正しく表示されることを確認
よくある問題と解決方法
Section titled “よくある問題と解決方法”CORSエラーが発生する場合
Section titled “CORSエラーが発生する場合”- エラーメッセージ例:
Access to fetch at 'http://localhost:8000/todos' from origin 'http://localhost:3000' has been blocked by CORS policy
- 解決方法:
app/main.py
のCORS設定を確認する- フロントエンドのURLが正しく設定されているか確認する
- バックエンドサーバーを再起動する
APIリクエストが失敗する場合
Section titled “APIリクエストが失敗する場合”- エラーメッセージ例:
Failed to fetch
- 解決方法:
- バックエンドサーバーが起動しているか確認する
API_BASE_URL
が正しいか確認する
Todoが表示されない場合
Section titled “Todoが表示されない場合”- バックエンドサーバーが起動しているか確認する
Todoの操作が失敗する場合
Section titled “Todoの操作が失敗する場合”- バックエンドサーバーが起動しているか確認する
- リクエストのURLとパラメータが正しいか確認する