Skip to content

Next.jsのフロントエンドからバックエンドへの通信#

Next.jsとPrismaでWebアプリケーションを作る3回目です。 最初からやりたい場合はナビゲーションから初回を開いてください。

前回までに、Next.jsプロジェクトの作成と、API Routes機能を使ったサーバーサイドの処理を学びました。今回は、フロントエンド(ページコンポーネント)からAPI Routesを呼び出す方法を学びます。

Next.jsの大きな強みの一つは、フロントエンドとバックエンドが同じプロジェクト内で管理できることです。これにより、従来のReact+Nestのように別々のサーバーを起動する必要がなく、プロキシ設定なども不要です。

クライアントコンポーネントの作成#

Next.jsのAppルーターでは、デフォルトでコンポーネントはサーバーコンポーネントとして扱われます。APIリクエストのようなクライアントサイドの処理を行うには、クライアントコンポーネントとして宣言する必要があります。

app/components/MessageLoader.tsxというファイルを作成します:

'use client'; // この行でクライアントコンポーネントと宣言

import { useState, useEffect } from 'react';

export default function MessageLoader() {
  const [message, setMessage] = useState("ロード中...");

  useEffect(() => {
    async function fetchMessage() {
      try {
        const response = await fetch('/api/hello');
        const data = await response.json();
        setMessage(data.message);
      } catch (error) {
        console.error('Error fetching message:', error);
        setMessage('メッセージの取得に失敗しました');
      }
    }

    fetchMessage();
  }, []); // 空の依存配列でコンポーネントがマウントされた時のみ実行

  return (
    <div className="p-4 border rounded bg-gray-50">
      <h2 className="text-lg font-bold">サーバーからのメッセージ:</h2>
      <p>{message}</p>
    </div>
  );
}

ここでのポイントは:

  1. 'use client'ディレクティブ - これによりこのコンポーネントはクライアントサイドでレンダリングされます
  2. useStateuseEffect - Reactのフックを使ってAPIからデータを取得し、コンポーネントの状態を管理します
  3. fetch - ブラウザのネイティブAPIを使ってバックエンドにリクエストを送ります

ページへのコンポーネント追加#

次に、このコンポーネントをapp/page.tsxに追加します:

import MessageLoader from './components/MessageLoader';

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
        <h1 className="text-4xl font-bold mb-8">Next.jsへようこそ</h1>
        <p className="mb-4">これは私の最初のNext.jsアプリです</p>
        <p className="mb-8">アンケートアプリケーションを作成していきます</p>

        <MessageLoader />
      </div>
    </main>
  );
}

開発サーバーが起動している状態でブラウザを確認すると、APIから取得したメッセージ(「Hello World! そして世界へ」)が表示されているはずです。

Next.jsでのAPI通信

データ送信の追加#

次に、サーバーにデータを送信するフォームを作ってみましょう。新しいクライアントコンポーネントapp/components/MessageSender.tsxを作成します:

'use client';

import { useState } from 'react';

export default function MessageSender() {
  const [inputMessage, setInputMessage] = useState('');
  const [response, setResponse] = useState<any>(null);
  const [isLoading, setIsLoading] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setIsLoading(true);

    try {
      const res = await fetch('/api/hello', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ message: inputMessage }),
      });

      const data = await res.json();
      setResponse(data);
    } catch (error) {
      console.error('Error sending message:', error);
      setResponse({ error: '送信に失敗しました' });
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <div className="mt-8 p-4 border rounded">
      <h2 className="text-lg font-bold mb-4">サーバーにメッセージを送信:</h2>

      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={inputMessage}
          onChange={(e) => setInputMessage(e.target.value)}
          placeholder="メッセージを入力"
          className="p-2 border rounded w-full mb-4"
          required
        />

        <button 
          type="submit" 
          className="px-4 py-2 bg-blue-500 text-white rounded"
          disabled={isLoading}
        >
          {isLoading ? '送信中...' : '送信'}
        </button>
      </form>

      {response && (
        <div className="mt-4 p-3 bg-gray-50 rounded">
          <h3 className="font-bold">サーバーの応答:</h3>
          <pre className="whitespace-pre-wrap">{JSON.stringify(response, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

次に、このコンポーネントもapp/page.tsxに追加します:

import MessageLoader from './components/MessageLoader';
import MessageSender from './components/MessageSender';

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
        <h1 className="text-4xl font-bold mb-8">Next.jsへようこそ</h1>
        <p className="mb-4">これは私の最初のNext.jsアプリです</p>
        <p className="mb-8">アンケートアプリケーションを作成していきます</p>

        <MessageLoader />
        <MessageSender />
      </div>
    </main>
  );
}

ブラウザを確認すると、メッセージを入力して送信できるフォームが表示されています。メッセージを入力して送信ボタンをクリックすると、サーバーからのレスポンスが表示されます。

サーバーアクション(Server Actions)の活用#

Next.jsの最新機能「Server Actions」を使うと、もっと簡単にサーバーとの通信が可能です。これはフォーム送信に特に便利です。

app/actions.tsというファイルを作成します:

'use server';

export async function submitFormAction(formData: FormData) {
  const message = formData.get('message');

  // サーバーサイドで処理を実行
  const result = {
    message: `サーバーアクションでメッセージを受信: ${message}`,
    timestamp: new Date().toISOString()
  };

  // 処理に時間がかかる操作をシミュレート
  await new Promise(resolve => setTimeout(resolve, 500));

  return result;
}

次に、このサーバーアクションを使うコンポーネントapp/components/ServerActionForm.tsxを作成します:

'use client';

import { useState } from 'react';
import { submitFormAction } from '../actions';

export default function ServerActionForm() {
  const [result, setResult] = useState<any>(null);
  const [isPending, setIsPending] = useState(false);

  async function handleSubmit(formData: FormData) {
    setIsPending(true);
    const result = await submitFormAction(formData);
    setResult(result);
    setIsPending(false);
  }

  return (
    <div className="mt-8 p-4 border rounded">
      <h2 className="text-lg font-bold mb-4">Server Actionを使った通信:</h2>

      <form action={handleSubmit}>
        <input
          type="text"
          name="message"
          placeholder="メッセージを入力"
          className="p-2 border rounded w-full mb-4"
          required
        />

        <button 
          type="submit" 
          className="px-4 py-2 bg-green-500 text-white rounded"
          disabled={isPending}
        >
          {isPending ? '送信中...' : '送信 (Server Action)'}
        </button>
      </form>

      {result && (
        <div className="mt-4 p-3 bg-gray-50 rounded">
          <h3 className="font-bold">サーバーの応答:</h3>
          <pre className="whitespace-pre-wrap">{JSON.stringify(result, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

このコンポーネントもapp/page.tsxに追加しましょう:

import MessageLoader from './components/MessageLoader';
import MessageSender from './components/MessageSender';
import ServerActionForm from './components/ServerActionForm';

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
        <h1 className="text-4xl font-bold mb-8">Next.jsへようこそ</h1>
        <p className="mb-4">これは私の最初のNext.jsアプリです</p>
        <p className="mb-8">アンケートアプリケーションを作成していきます</p>

        <MessageLoader />
        <MessageSender />
        <ServerActionForm />
      </div>
    </main>
  );
}

まとめ#

Next.jsでは以下の方法でフロントエンドとバックエンドの通信が可能です:

  1. APIルートへのfetch: 標準的なfetch APIを使ってAPI Routesにリクエストを送る方法
  2. Server Actions: フォームデータの送信や処理をサーバー側で直接処理する方法

従来のReact+Nestの構成と比べて、Next.jsでは:

  • プロジェクトが一つにまとまる
  • プロキシ設定が不要
  • 開発サーバーが一つで済む
  • TypeScriptの型定義をフロントエンドとバックエンドで共有できる
  • Server Actionsのような効率的な通信手段が使える

これらの利点により、開発効率が大幅に向上します。

次は、Prismaを使ってデータベース操作を実装していきましょう。