Triển khai ứng dụng Next.js toàn stack lên Cloud Run bằng Firestore thông qua SDK quản trị Node.js

Triển khai ứng dụng Next.js toàn stack lên Cloud Run bằng Firestore thông qua SDK quản trị Node.js

Thông tin về lớp học lập trình này

subjectLần cập nhật gần đây nhất: thg 4 11, 2025
account_circleTác giả: Luke Schlangen

1. Tổng quan

Cloud Run là một nền tảng được quản lý toàn diện, cho phép bạn chạy mã trực tiếp trên cơ sở hạ tầng có thể mở rộng của Google. Lớp học lập trình này sẽ minh hoạ cách kết nối ứng dụng Next.js trên Cloud Run với cơ sở dữ liệu Firestore bằng SDK Quản trị của Node.js.

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách:

  • Tạo cơ sở dữ liệu Firestore
  • Triển khai một ứng dụng trên Cloud Run để kết nối với cơ sở dữ liệu Firestore

2. Điều kiện tiên quyết

  1. Nếu chưa có Tài khoản Google, bạn phải tạo Tài khoản Google.
    • Sử dụng tài khoản cá nhân thay vì tài khoản công việc hoặc trường học. Tài khoản công việc và tài khoản trường học có thể có các quy định hạn chế khiến bạn không bật được các API cần thiết cho lớp học này.

3. Thiết lập dự án

  1. Đăng nhập vào Google Cloud Console.
  2. Bật tính năng thanh toán trong Cloud Console.
    • Bạn sẽ mất chưa đến 1 USD để hoàn thành lớp học này về tài nguyên trên đám mây.
    • Bạn có thể làm theo các bước ở cuối lớp học này để xoá tài nguyên nhằm tránh bị tính thêm phí.
    • Người dùng mới đủ điều kiện dùng thử miễn phí 300 USD.
  3. Tạo dự án mới hoặc chọn sử dụng lại một dự án hiện có.

4. Mở Trình chỉnh sửa Cloud Shell

  1. Chuyển đến Trình chỉnh sửa Cloud Shell
  2. Nếu thiết bị đầu cuối không xuất hiện ở cuối màn hình, hãy mở thiết bị đầu cuối:
    • Nhấp vào trình đơn có biểu tượng ba dấu gạch ngang Biểu tượng trình đơn có ba đường kẻ
    • Nhấp vào Terminal (Thiết bị đầu cuối)
    • Nhấp vào New Terminal (Thiết bị đầu cuối mới)Mở cửa sổ dòng lệnh mới trong Trình chỉnh sửa Cloud Shell
  3. Trong dòng lệnh, hãy thiết lập dự án bằng lệnh sau:
    • Định dạng:
      gcloud config set project [PROJECT_ID]
    • Ví dụ:
      gcloud config set project lab-project-id-example
    • Nếu bạn không nhớ mã dự án của mình:
      • Bạn có thể liệt kê tất cả mã dự án bằng:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
      Đặt mã dự án trong thiết bị đầu cuối của Trình chỉnh sửa Cloud Shell
  4. Nếu bạn được nhắc uỷ quyền, hãy nhấp vào Uỷ quyền để tiếp tục. Nhấp để uỷ quyền cho Cloud Shell
  5. Bạn sẽ thấy thông báo này:
    Updated property [core/project].
    
    Nếu bạn thấy WARNING và được hỏi Do you want to continue (Y/N)?, thì có thể bạn đã nhập mã dự án không chính xác. Nhấn N, nhấn Enter rồi thử chạy lại lệnh gcloud config set project.

5. Bật API

Trong dòng lệnh, hãy bật các API:

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

Nếu bạn được nhắc uỷ quyền, hãy nhấp vào Uỷ quyền để tiếp tục. Nhấp để uỷ quyền cho Cloud Shell

Quá trình thực thi lệnh này có thể mất vài phút, nhưng cuối cùng sẽ tạo ra thông báo thành công tương tự như sau:

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

6. Tạo cơ sở dữ liệu Firestore

  1. Chạy lệnh gcloud firestore databases create để tạo cơ sở dữ liệu firestore
    gcloud firestore databases create --location=nam5

7. Chuẩn bị ứng dụng

Chuẩn bị một ứng dụng Next.js phản hồi các yêu cầu HTTP.

  1. Để tạo một dự án Next.js mới có tên là task-app, hãy sử dụng lệnh:
    npx --yes create-next-app@15.2.4 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
  2. Thay đổi thư mục thành task-app:
    cd task-app
  1. Cài đặt firebase-admin để tương tác với cơ sở dữ liệu Firestore.
    npm install firebase-admin
  1. Mở tệp actions.ts trong Trình chỉnh sửa Cloud Shell:
    cloudshell edit app/actions.ts
    Giờ đây, một tệp trống sẽ xuất hiện ở phần trên cùng của màn hình. Đây là nơi bạn có thể chỉnh sửa tệp actions.ts này. Hiển thị mã đó ở phần trên cùng của màn hình
  2. Sao chép mã sau và dán vào tệp actions.ts đã mở:
    '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. Mở tệp page.tsx trong Trình chỉnh sửa Cloud Shell:
    cloudshell edit app/page.tsx
    Tệp hiện có sẽ xuất hiện ở phần trên cùng của màn hình. Đây là nơi bạn có thể chỉnh sửa tệp page.tsx này. Hiển thị mã đó ở phần trên cùng của màn hình
  2. Xoá nội dung hiện có của tệp page.tsx.
  3. Sao chép mã sau và dán vào tệp page.tsx đã mở:
    '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>
      );
    }

Ứng dụng hiện đã sẵn sàng để triển khai.

8. Triển khai ứng dụng trên Cloud Run

  1. Chạy lệnh dưới đây để triển khai ứng dụng của bạn lên Cloud Run:
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=.
  2. Nếu được nhắc, hãy nhấn YEnter để xác nhận rằng bạn muốn tiếp tục:
    Do you want to continue (Y/n)? Y
    

Sau vài phút, ứng dụng sẽ cung cấp một URL để bạn truy cập.

Chuyển đến URL để xem ứng dụng của bạn hoạt động. Mỗi khi truy cập vào URL hoặc làm mới trang, bạn sẽ thấy ứng dụng công việc.

9. Xin chúc mừng

Trong lớp học lập trình này, bạn đã tìm hiểu cách thực hiện các việc sau:

  • Tạo phiên bản Cloud SQL cho PostgreSQL
  • Triển khai một ứng dụng trên Cloud Run để kết nối với cơ sở dữ liệu Cloud SQL

Dọn dẹp

Cloud SQL không có cấp miễn phí và sẽ tính phí nếu bạn tiếp tục sử dụng. Bạn có thể xoá dự án trên Google Cloud để tránh bị tính thêm phí.

Mặc dù Cloud Run không tính phí khi bạn không sử dụng dịch vụ, nhưng bạn vẫn có thể phải trả phí để lưu trữ hình ảnh vùng chứa trong CSDL cấu phần phần mềm. Việc xoá dự án trên Google Cloud sẽ dừng tính phí cho tất cả tài nguyên được sử dụng trong dự án đó.

Nếu bạn muốn, hãy xoá dự án:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Bạn cũng có thể xoá các tài nguyên không cần thiết khỏi ổ đĩa cloudshell. Bạn có thể:

  1. Xoá thư mục dự án của lớp học lập trình:
    rm -rf ~/task-app
  2. Cảnh báo! Bạn không thể huỷ thao tác tiếp theo! Nếu muốn xoá mọi thứ trên Cloud Shell để giải phóng dung lượng, bạn có thể xoá toàn bộ thư mục gốc. Hãy lưu mọi thứ bạn muốn giữ lại ở nơi khác.
    sudo rm -rf $HOME

Tiếp tục học hỏi