Node.js Admin SDK を使用して、Firestore でフルスタックの Next.js アプリケーションを Cloud Run にデプロイする

Node.js Admin SDK を使用して、Firestore でフルスタックの Next.js アプリケーションを Cloud Run にデプロイする

この Codelab について

subject最終更新: 4月 11, 2025
account_circle作成者: Luke Schlangen

1. 概要

Cloud Run は、フルマネージド プラットフォームで、Google のスケーラブルなインフラストラクチャ上で直接コードを実行できます。この Codelab では、Node.js Admin SDK を使用して、Cloud Run 上の Next.js アプリケーションを Firestore データベースに接続する方法について説明します。

このラボでは、次の方法について学びます。

  • Firestore データベースを作成する
  • Firestore データベースに接続するアプリケーションを Cloud Run にデプロイする

2. 前提条件

  1. Google アカウントをお持ちでない場合は、Google アカウントを作成する必要があります。
    • 仕事用または学校用のアカウントではなく、個人アカウントを使用している。職場用アカウントや学校用アカウントには、このラボに必要な API を有効にできない制限が適用されている場合があります。

3. プロジェクトの設定

  1. Google Cloud コンソールにログインします。
  2. Cloud コンソールで課金を有効にする
    • このラボを完了しても、Cloud リソースの費用は 1 米ドル未満です。
    • このラボの最後にある手順に沿ってリソースを削除すると、それ以上の請求が発生しなくなります。
    • 新規ユーザーは、300 米ドル分の無料トライアルをご利用いただけます。
  3. 新しいプロジェクトを作成するか、既存のプロジェクトを再利用するかを選択します。

4. Cloud Shell エディタを開く

  1. Cloud Shell エディタに移動します。
  2. ターミナルが画面の下部に表示されない場合は、開きます。
    • ハンバーガー メニュー ハンバーガー メニュー アイコン をクリックします。
    • [Terminal] をクリックします。
    • [New Terminal] をクリックします。Cloud Shell エディタで新しいターミナルを開く
  3. ターミナルで、次のコマンドを使用してプロジェクトを設定します。
    • 形式:
      gcloud config set project [PROJECT_ID]
    • 例:
      gcloud config set project lab-project-id-example
    • プロジェクト ID がわからない場合:
      • すべてのプロジェクト ID を一覧表示するには、次のコマンドを使用します。
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
      Cloud Shell エディタのターミナルでプロジェクト ID を設定する
  4. 承認を求められたら、[承認] をクリックして続行します。クリックして Cloud Shell を承認する
  5. 次のようなメッセージが表示されます。
    Updated property [core/project].
    
    WARNING が表示され、Do you want to continue (Y/N)? を求められた場合は、プロジェクト ID が正しく入力されていない可能性があります。N キー、Enter キーを押して、gcloud config set project コマンドをもう一度実行してみてください。

5. API を有効にする

ターミナルで、API を有効にします。

gcloud services enable \
  firestore.googleapis.com \
  run.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com

承認を求められたら、[承認] をクリックして続行します。クリックして Cloud Shell を承認する

このコマンドが完了するまで数分かかる場合がありますが、最終的には次のような成功メッセージが表示されます。

Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.

6. Firestore データベースを作成する

  1. gcloud firestore databases create コマンドを実行して Firestore データベースを作成します。
    gcloud firestore databases create --location=nam5

7. 申請書類を準備する

HTTP リクエストに応答する Next.js アプリケーションを準備します。

  1. task-app という名前の新しい Next.js プロジェクトを作成するには、次のコマンドを使用します。
    npx --yes create-next-app@15.2.4 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
  2. ディレクトリを task-app に変更します。
    cd task-app
  1. firebase-admin をインストールして、Firestore データベースを操作します。
    npm install firebase-admin
  1. actions.ts ファイルを Cloud Shell エディタで開きます。
    cloudshell edit app/actions.ts
    画面上部に空のファイルが表示されます。この actions.ts ファイルを編集できます。コードが画面の上部に表示されること
  2. 次のコードをコピーして、開いた actions.ts ファイルに貼り付けます。
    'use server'
    import { initializeApp, applicationDefault, getApps } from 'firebase-admin/app';
    import { getFirestore } from 'firebase-admin/firestore';
    const credential = applicationDefault();

    // Only initialize app if it does not already exist
    if (getApps().length === 0) {
      initializeApp({ credential });
    }

    const db = getFirestore();
    const tasksRef = db.collection('tasks');

    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
      createdAt: number;
    };

    // CREATE
    export async function addNewTaskToDatabase(newTask: string) {
      await tasksRef.doc().create({
        title: newTask,
        status: 'IN_PROGRESS',
        createdAt: Date.now(),
      });
      return;
    }

    // READ
    export async function getTasksFromDatabase() {
      const snapshot = await tasksRef.orderBy('createdAt', 'desc').limit(100).get();
      const tasks = await snapshot.docs.map(doc => ({
        id: doc.id,
        title: doc.data().title,
        status: doc.data().status,
        createdAt: doc.data().createdAt,
      }));
      return tasks;
    }

    // UPDATE
    export async function updateTaskInDatabase(task: Task) {
      await tasksRef.doc(task.id).set(task);
      return;
    }

    // DELETE
    export async function deleteTaskFromDatabase(taskId: string) {
      await tasksRef.doc(taskId).delete();
      return;
    }
  1. page.tsx ファイルを Cloud Shell エディタで開きます。
    cloudshell edit app/page.tsx
    画面上部に既存のファイルが表示されます。この page.tsx ファイルを編集できます。コードが画面の上部に表示されること
  2. page.tsx ファイルの既存の内容を削除します。
  3. 次のコードをコピーして、開いた page.tsx ファイルに貼り付けます。
    'use client'
    import React, { useEffect, useState } from "react";
    import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions";

    type Task = {
      id: string;
      title: string;
      status: 'IN_PROGRESS' | 'COMPLETE';
      createdAt: number;
    };

    export default function Home() {
      const [newTaskTitle, setNewTaskTitle] = useState('');
      const [tasks, setTasks] = useState<Task[]>([]);

      async function getTasks() {
        const updatedListOfTasks = await getTasksFromDatabase();
        setTasks(updatedListOfTasks);
      }

      useEffect(() => {
        getTasks();
      }, []);

      async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault();
        await addNewTaskToDatabase(newTaskTitle);
        await getTasks();
        setNewTaskTitle('');
      };

      async function updateTask(task: Task, newTaskValues: Partial<Task>) {
        await updateTaskInDatabase({ ...task, ...newTaskValues });
        await getTasks();
      }

      async function deleteTask(taskId: string) {
        await deleteTaskFromDatabase(taskId);
        await getTasks();
      }

      return (
        <main className="p-4">
          <h2 className="text-2xl font-bold mb-4">To Do List</h2>
          <div className="flex mb-4">
            <form onSubmit={handleSubmit} className="flex mb-8">
              <input
                type="text"
                placeholder="New Task Title"
                value={newTaskTitle}
                onChange={(e) => setNewTaskTitle(e.target.value)}
                className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit"
              />
              <button
                type="submit"
                className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap"
              >
                Add New Task
              </button>
            </form>
          </div>
          <table className="w-full">
            <tbody>
              {tasks.map(function (task) {
                const isComplete = task.status === 'COMPLETE';
                return (
                  <tr key={task.id} className="border-b border-gray-200">
                    <td className="py-2 px-4">
                      <input
                        type="checkbox"
                        checked={isComplete}
                        onChange={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })}
                        className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500"
                      />
                    </td>
                    <td className="py-2 px-4">
                      <span
                        className={`transition-all duration-300 ease-in-out ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`}
                      >
                        {task.title}
                      </span>
                    </td>
                    <td className="py-2 px-4">
                      <button
                        onClick={() => deleteTask(task.id)}
                        className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right"
                      >
                        Delete
                      </button>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </main>
      );
    }

これで、アプリケーションをデプロイする準備が整いました。

8. アプリケーションを Cloud Run にデプロイする

  1. 次のコマンドを実行して、アプリケーションを Cloud Run にデプロイします。
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=.
  2. メッセージが表示されたら、YEnter を押して続行することを確認します。
    Do you want to continue (Y/n)? Y
    

数分後、アクセスする URL がアプリケーションに表示されます。

URL に移動して、アプリケーションの動作を確認します。URL にアクセスするたび、またはページを更新するたびに、タスクアプリが表示されます。

9. 完了

このラボでは、以下の操作について学習しました。

  • Cloud SQL for PostgreSQL インスタンスを作成する
  • Cloud SQL データベースに接続するアプリケーションを Cloud Run にデプロイする

クリーンアップ

Cloud SQL には無料枠がないため、引き続き使用すると料金が発生します。Cloud プロジェクトを削除して、追加料金が発生しないようにすることができます。

サービスが使用されていない場合、Cloud Run の料金は発生しませんが、コンテナ イメージを Artifact Registry に保存すると課金される場合があります。Cloud プロジェクトを削除すると、そのプロジェクト内で使用されているすべてのリソースに対する課金が停止します。

必要に応じて、プロジェクトを削除します。

gcloud projects delete $GOOGLE_CLOUD_PROJECT

cloudshell ディスクから不要なリソースを削除することもできます。次のことが可能です。

  1. Codelab プロジェクト ディレクトリを削除します。
    rm -rf ~/task-app
  2. 警告: 次の操作は元に戻せません。Cloud Shell 上のすべてのファイルを削除して空き容量を確保する場合は、ホーム ディレクトリ全体を削除できます。残しておきたいものはすべて別の場所に保存してください。
    sudo rm -rf $HOME

学習を継続