目次


    1. データの「読み取り」(Read)

    まず、既存のデータを表示するところから始めましょう。

    考え方:

    Next.js のコンポーネント(または RSC - React Server Components)でデータをフェッチし、表示します。API ルートを使う場合は、フロントエンドからその API ルートを呼び出します。

    実装例(API ルート + クライアントコンポーネント):

    JavaScript
    // app/api/tasks/route.js (Route Handler)
    import { NextResponse } from 'next/server';
    import prisma from '@/lib/prisma'; // PrismaなどのORMを想定
    
    export async function GET() {
      try {
        const tasks = await prisma.task.findMany(); // 全てのタスクを取得
        return NextResponse.json(tasks);
      } catch (error) {
        return NextResponse.json({ error: 'Failed to fetch tasks' }, { status: 500 });
      }
    }
    
    JavaScript
    // app/tasks/page.jsx (Client Component)
    'use client'; // クライアントコンポーネントであることを宣言
    
    import { useEffect, useState } from 'react';
    
    export default function TasksPage() {
      const [tasks, setTasks] = useState([]);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        async function fetchTasks() {
          try {
            const response = await fetch('/api/tasks'); // APIルートを呼び出し
            if (!response.ok) {
              throw new Error('Failed to fetch tasks');
            }
            const data = await response.json();
            setTasks(data);
          } catch (err) {
            setError(err.message);
          } finally {
            setLoading(false);
          }
        }
        fetchTasks();
      }, []);
    
      if (loading) return <p>Loading tasks...</p>;
      if (error) return <p>Error: {error}</p>;
    
      return (
        <div>
          <h1>My Tasks</h1>
          <ul>
            {tasks.map((task) => (
              <li key={task.id}>{task.title}</li>
            ))}
          </ul>
        </div>
      );
    }
    

    ポイント:

    • app/api/tasks/route.jsGET リクエストを処理する API ルートを作成。
    • app/tasks/page.jsx のクライアントコンポーネントで useEffect を使ってデータをフェッチ。
    • Prisma のような ORM を利用すると、データベース操作が簡単になります。

    2. データの「作成」(Create)

    新しいデータを追加する方法です。

    考え方:

    フォームからユーザー入力を受け取り、API ルートに POST リクエストとして送信します。

    実装例:

    JavaScript
    // app/api/tasks/route.js (POSTメソッドを追加)
    // ... GETメソッドの上に追記 ...
    export async function POST(request) {
      try {
        const { title } = await request.json(); // リクエストボディからタイトルを取得
        const newTask = await prisma.task.create({
          data: { title },
        });
        return NextResponse.json(newTask, { status: 201 }); // 成功時は201 Created
      } catch (error) {
        return NextResponse.json({ error: 'Failed to create task' }, { status: 500 });
      }
    }
    
    JavaScript
    // app/tasks/page.jsx (フォームを追加)
    // ... 既存のコードの下に追記 ...
    import { useState } from 'react'; // useStateをインポート
    
    export default function TasksPage() {
      // ... 既存のtasks, loading, error state ...
      const [newTaskTitle, setNewTaskTitle] = useState('');
    
      const handleSubmit = async (e) => {
        e.preventDefault();
        try {
          const response = await fetch('/api/tasks', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title: newTaskTitle }),
          });
          if (!response.ok) {
            throw new Error('Failed to create task');
          }
          const createdTask = await response.json();
          setTasks((prevTasks) => [...prevTasks, createdTask]); // UIを更新
          setNewTaskTitle(''); // フォームをクリア
        } catch (err) {
          setError(err.message);
        }
      };
    
      return (
        <div>
          {/* ... 既存のタスクリスト ... */}
    
          <h2>Add New Task</h2>
          <form onSubmit={handleSubmit}>
            <input
              type="text"
              value={newTaskTitle}
              onChange={(e) => setNewTaskTitle(e.target.value)}
              placeholder="Task title"
              required
            />
            <button type="submit">Add Task</button>
          </form>
        </div>
      );
    }
    

    ポイント:

    • API ルートに POST メソッドを追加し、リクエストボディからデータを受け取ります。
    • クライアントコンポーネントでフォームを作成し、fetch API を使って POST リクエストを送信します。
    • 成功したら、ステートを更新して UI に新しいタスクを表示します。

    3. データの「更新」(Update)

    既存のデータを変更する方法です。

    考え方:

    特定のデータ(IDで識別)を対象に、PUT または PATCH リクエストを送信します。

    実装例(特定のタスクを更新するAPI ルート):

    JavaScript
    // app/api/tasks/[id]/route.js (新しいRoute Handlerファイル)
    import { NextResponse } from 'next/server';
    import prisma from '@/lib/prisma';
    
    export async function PUT(request, { params }) {
      const { id } = params;
      const { title } = await request.json(); // 更新するデータ
      try {
        const updatedTask = await prisma.task.update({
          where: { id: parseInt(id) }, // IDでタスクを特定
          data: { title },
        });
        return NextResponse.json(updatedTask);
      } catch (error) {
        return NextResponse.json({ error: 'Failed to update task' }, { status: 500 });
      }
    }
    
    JavaScript
    // app/tasks/page.jsx (更新ボタンとロジックを追加)
    // ... 既存のコードの下に追記 ...
    export default function TasksPage() {
      // ... 既存のtasks, loading, error state ...
      const [editingTaskId, setEditingTaskId] = useState(null);
      const [editingTaskTitle, setEditingTaskTitle] = useState('');
    
      const handleEdit = (task) => {
        setEditingTaskId(task.id);
        setEditingTaskTitle(task.title);
      };
    
      const handleUpdate = async (taskId) => {
        try {
          const response = await fetch(`/api/tasks/${taskId}`, {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ title: editingTaskTitle }),
          });
          if (!response.ok) {
            throw new Error('Failed to update task');
          }
          const updatedTask = await response.json();
          setTasks((prevTasks) =>
            prevTasks.map((task) => (task.id === taskId ? updatedTask : task))
          ); // UIを更新
          setEditingTaskId(null); // 編集モードを終了
          setEditingTaskTitle('');
        } catch (err) {
          setError(err.message);
        }
      };
    
      return (
        <div>
          <h1>My Tasks</h1>
          <ul>
            {tasks.map((task) => (
              <li key={task.id}>
                {editingTaskId === task.id ? (
                  <>
                    <input
                      type="text"
                      value={editingTaskTitle}
                      onChange={(e) => setEditingTaskTitle(e.target.value)}
                    />
                    <button onClick={() => handleUpdate(task.id)}>Save</button>
                    <button onClick={() => setEditingTaskId(null)}>Cancel</button>
                  </>
                ) : (
                  <>
                    {task.title}
                    <button onClick={() => handleEdit(task)}>Edit</button>
                  </>
                )}
              </li>
            ))}
          </ul>
          {/* ... Add New Task フォーム ... */}
        </div>
      );
    }
    

    ポイント:

    • 動的なルートセグメント [id] を使って特定のタスクを対象にする API ルートを作成。
    • PUT メソッドで更新データを送信。
    • UI では、編集ボタンを押すと入力フィールドが表示され、保存ボタンで更新を実行します。

    4. データの「削除」(Delete)

    不要なデータを消去する方法です。

    考え方:

    特定のデータ(IDで識別)を対象に、DELETE リクエストを送信します。

    実装例(特定のタスクを削除するAPI ルート):

    JavaScript
    // app/api/tasks/[id]/route.js (DELETEメソッドを追加)
    // ... PUTメソッドの下に追記 ...
    export async function DELETE(request, { params }) {
      const { id } = params;
      try {
        await prisma.task.delete({
          where: { id: parseInt(id) },
        });
        return NextResponse.json({ message: 'Task deleted successfully' }, { status: 200 });
      } catch (error) {
        return NextResponse.json({ error: 'Failed to delete task' }, { status: 500 });
      }
    }
    
    JavaScript
    // app/tasks/page.jsx (削除ボタンとロジックを追加)
    // ... 既存のコードの下に追記 ...
    export default function TasksPage() {
      // ... 既存のtasks, loading, error state ...
    
      const handleDelete = async (taskId) => {
        try {
          const response = await fetch(`/api/tasks/${taskId}`, {
            method: 'DELETE',
          });
          if (!response.ok) {
            throw new Error('Failed to delete task');
          }
          setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId)); // UIから削除
        } catch (err) {
          setError(err.message);
        }
      };
    
      return (
        <div>
          <h1>My Tasks</h1>
          <ul>
            {tasks.map((task) => (
              <li key={task.id}>
                {/* ... 編集UIまたはタスク表示 ... */}
                {editingTaskId !== task.id && ( // 編集中でない場合のみ削除ボタンを表示
                  <button onClick={() => handleDelete(task.id)}>Delete</button>
                )}
              </li>
            ))}
          </ul>
          {/* ... Add New Task フォーム ... */}
        </div>
      );
    }
    

    ポイント:

    • API ルートに DELETE メソッドを追加し、URL パラメータの ID を使ってタスクを削除。
    • fetch API で DELETE リクエストを送信し、成功したら UI から該当タスクを削除します。

    まとめ

    このブログ記事では、Next.js での CRUD 操作の基本的な実装方法を解説しました。

    • Read: API ルートでデータを取得し、コンポーネントで表示。
    • Create: フォームからデータを入力し、API ルートに POST
    • Update: 特定のデータを対象に、API ルートに PUT または PATCH
    • Delete: 特定のデータを対象に、API ルートに DELETE
    PREV
    2025.06.06
    今更聞けないES6-mapメソッド/filterメソッド
    NEXT
    2025.06.10
    【保存版】要件定義とは?非機能要件との違いもわかりやすく解説