Skip to content

1: 画面とAPIの連携

このステップでは、Next.jsで作成したフロントエンドとFastAPIで作成したバックエンドを連携させる方法を学びます。

このステップの手順を実施するには Next.js チュートリアルと FastAPI チュートリアルが完了している必要があります。

todo-api/app/main.pyを編集して CORS の設定を追加します:

todo-api/app/main.py
from fastapi import FastAPI
from 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からのアクセスを許可する」と設定する必要があります。

src/app/api/todos.jsを作成し、以下のコードを作成します:

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();
}

src/app/todos/page.jsを以下のように更新します:

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>
);
}
  1. バックエンドサーバーの起動:

    Terminal window
    cd todo-api
    source venv/bin/activate # Windows: venv\Scripts\activate
    uvicorn app.main:app --reload

    サーバーはhttp://localhost:8000で起動します。

  2. フロントエンドサーバーの起動:

    Terminal window
    cd todo-manager
    npm run dev

    サーバーはhttp://localhost:3000で起動します。

ブラウザで http://localhost:3000/todos を開き、以下の機能を順番にテストします:

  1. Todoの作成

    • テキストボックスに「買い物に行く」と入力
    • 「追加」ボタンをクリック
    • 新しいTodoがリストに表示されることを確認
    • テキストボックスが空になることを確認
  2. Todoの完了状態の確認

    • 作成したTodoのチェックボックスをクリック
    • Todoのテキストに取り消し線が表示されることを確認
    • もう一度チェックボックスをクリックしても、取り消し線は消えないことを確認(=一度完了にしたら未完了には戻せない)
  3. Todoの削除

    • Todoの「削除」ボタンをクリック
    • 確認ダイアログで「OK」をクリック
    • Todoがリストから消えることを確認
  1. バックエンドサーバーを停止

    • ターミナルで Ctrl+C を押してサーバーを停止
    • フロントエンドでTodoの追加・完了・削除を試みる
    • エラーメッセージがコンソールに表示されることを確認
  2. バックエンドサーバーを再起動

    • サーバーを再起動
    • フロントエンドの操作が正常に動作することを確認
    • Todoリストが正しく表示されることを確認
  • エラーメッセージ例:Access to fetch at 'http://localhost:8000/todos' from origin 'http://localhost:3000' has been blocked by CORS policy
  • 解決方法:
    1. app/main.py のCORS設定を確認する
    2. フロントエンドのURLが正しく設定されているか確認する
    3. バックエンドサーバーを再起動する
  • エラーメッセージ例:Failed to fetch
  • 解決方法:
    1. バックエンドサーバーが起動しているか確認する
    2. API_BASE_URLが正しいか確認する
  1. バックエンドサーバーが起動しているか確認する
  1. バックエンドサーバーが起動しているか確認する
  2. リクエストのURLとパラメータが正しいか確認する