Men-deploy aplikasi Next.js stack lengkap ke Cloud Run dengan Firestore menggunakan Node.js Admin SDK

Men-deploy aplikasi Next.js stack lengkap ke Cloud Run dengan Firestore menggunakan Node.js Admin SDK

Tentang codelab ini

subjectTerakhir diperbarui Apr 11, 2025
account_circleDitulis oleh Luke Schlangen

1. Ringkasan

Cloud Run adalah platform terkelola sepenuhnya yang memungkinkan Anda menjalankan kode langsung di atas infrastruktur Google yang skalabel. Codelab ini akan menunjukkan cara menghubungkan aplikasi Next.js di Cloud Run ke database Firestore menggunakan Node.js Admin SDK.

Di lab ini, Anda akan mempelajari cara:

  • Membuat database Firestore
  • Men-deploy aplikasi ke Cloud Run yang terhubung ke database Firestore Anda

2. Prasyarat

  1. Jika belum memiliki Akun Google, Anda harus membuat Akun Google.
    • Gunakan akun pribadi, bukan akun kantor atau sekolah. Akun kerja dan sekolah mungkin memiliki batasan yang mencegah Anda mengaktifkan API yang diperlukan untuk lab ini.

3. Penyiapan project

  1. Login ke Konsol Google Cloud.
  2. Aktifkan penagihan di Konsol Cloud.
    • Menyelesaikan lab ini akan menghabiskan biaya kurang dari $1 USD untuk resource Cloud.
    • Anda dapat mengikuti langkah-langkah di akhir lab ini untuk menghapus resource guna menghindari tagihan lebih lanjut.
    • Pengguna baru memenuhi syarat untuk Uji Coba Gratis senilai$300 USD.
  3. Buat project baru atau pilih untuk menggunakan kembali project yang ada.

4. Membuka Cloud Shell Editor

  1. Buka Cloud Shell Editor
  2. Jika terminal tidak muncul di bagian bawah layar, buka:
    • Klik menu tiga garis Ikon menu tiga garis
    • Klik Terminal
    • Klik New TerminalMembuka terminal baru di Cloud Shell Editor
  3. Di terminal, tetapkan project Anda dengan perintah ini:
    • Format:
      gcloud config set project [PROJECT_ID]
    • Contoh:
      gcloud config set project lab-project-id-example
    • Jika Anda tidak dapat mengingat project ID:
      • Anda dapat mencantumkan semua project ID dengan:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
      Menetapkan project ID di terminal Cloud Shell Editor
  4. Jika diminta untuk memberikan otorisasi, klik Authorize untuk melanjutkan. Klik untuk memberikan otorisasi pada Cloud Shell
  5. Anda akan melihat pesan ini:
    Updated property [core/project].
    
    Jika Anda melihat WARNING dan ditanya Do you want to continue (Y/N)?, berarti Anda mungkin salah memasukkan project ID. Tekan N, tekan Enter, lalu coba jalankan kembali perintah gcloud config set project.

5. Mengaktifkan API

Di terminal, aktifkan API:

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

Jika diminta untuk memberikan otorisasi, klik Authorize untuk melanjutkan. Klik untuk memberikan otorisasi pada Cloud Shell

Pemrosesan perintah ini mungkin memerlukan waktu beberapa menit, tetapi pada akhirnya akan menampilkan pesan berhasil seperti ini:

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

6. Membuat Database Firestore

  1. Jalankan perintah gcloud firestore databases create untuk membuat database firestore
    gcloud firestore databases create --location=nam5

7. Menyiapkan Aplikasi

Siapkan aplikasi Next.js yang merespons permintaan HTTP.

  1. Untuk membuat project Next.js baru bernama task-app, gunakan perintah:
    npx --yes create-next-app@15.2.4 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
  2. Ubah direktori menjadi task-app:
    cd task-app
  1. Instal firebase-admin untuk berinteraksi dengan database Firestore.
    npm install firebase-admin
  1. Buka file actions.ts di Cloud Shell Editor:
    cloudshell edit app/actions.ts
    File kosong akan muncul di bagian atas layar. Di sinilah Anda dapat mengedit file actions.ts ini. Tampilkan kode tersebut di bagian atas layar
  2. Salin kode berikut dan tempelkan ke dalam file actions.ts yang terbuka:
    '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. Buka file page.tsx di Cloud Shell Editor:
    cloudshell edit app/page.tsx
    File yang ada sekarang akan muncul di bagian atas layar. Di sinilah Anda dapat mengedit file page.tsx ini. Tampilkan kode tersebut di bagian atas layar
  2. Hapus konten file page.tsx yang ada.
  3. Salin kode berikut dan tempelkan ke dalam file page.tsx yang terbuka:
    '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>
      );
    }

Aplikasi kini siap di-deploy.

8. Men-deploy aplikasi ke Cloud Run

  1. Jalankan perintah di bawah untuk men-deploy aplikasi Anda ke Cloud Run:
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=.
  2. Jika diminta, tekan Y dan Enter untuk mengonfirmasi bahwa Anda ingin melanjutkan:
    Do you want to continue (Y/n)? Y
    

Setelah beberapa menit, aplikasi akan memberikan URL yang dapat Anda kunjungi.

Buka URL untuk melihat cara kerja aplikasi Anda. Setiap kali Anda mengunjungi URL atau memuat ulang halaman, Anda akan melihat aplikasi tugas.

9. Selamat

Di lab ini, Anda telah mempelajari cara melakukan hal-hal berikut:

  • Membuat instance Cloud SQL for PostgreSQL
  • Men-deploy aplikasi ke Cloud Run yang terhubung ke database Cloud SQL Anda

Pembersihan

Cloud SQL tidak memiliki paket gratis dan akan menagih Anda jika Anda terus menggunakannya. Anda dapat menghapus project Cloud untuk menghindari tagihan tambahan.

Meskipun Cloud Run tidak mengenakan biaya saat layanannya tidak digunakan, Anda mungkin tetap ditagih atas penyimpanan image container di Artifact Registry. Menghapus project Cloud akan menghentikan penagihan untuk semua resource yang digunakan dalam project tersebut.

Jika mau, hapus project:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Anda juga dapat menghapus resource yang tidak diperlukan dari disk cloudshell. Anda dapat:

  1. Hapus direktori project codelab:
    rm -rf ~/task-app
  2. Peringatan! Tindakan berikutnya ini tidak dapat diurungkan. Jika ingin menghapus semua yang ada di Cloud Shell untuk mengosongkan ruang, Anda dapat menghapus seluruh direktori beranda. Pastikan semua yang ingin Anda simpan disimpan di tempat lain.
    sudo rm -rf $HOME

Terus belajar