Esegui il deployment di un'applicazione Next.js full stack in Cloud Run con Firestore utilizzando l'SDK Admin di Node.js

1. Panoramica

Cloud Run è una piattaforma completamente gestita che consente di eseguire il codice direttamente sull'infrastruttura scalabile di Google. Questo Codelab mostrerà come connettere un'applicazione Next.js su Cloud Run a un database Firestore utilizzando l'SDK Node.js Admin.

In questo lab, imparerai a:

  • Crea un database Firestore
  • Esegui il deployment di un'applicazione in Cloud Run che si connette al tuo database Firestore

2. Prerequisiti

  1. Se non hai ancora un Account Google, devi crearne uno.
    • Utilizza un account personale anziché un account di lavoro o della scuola. Gli account di lavoro e della scuola potrebbero avere limitazioni che impediscono l'attivazione delle API necessarie per questo lab.

3. Configurazione del progetto

  1. Accedi alla console Google Cloud.
  2. Abilita la fatturazione nella console Cloud.
    • Il completamento di questo lab dovrebbe costare meno di 1 $in risorse cloud.
    • Per evitare ulteriori addebiti, puoi seguire i passaggi alla fine di questo lab per eliminare le risorse.
    • I nuovi utenti hanno diritto alla prova senza costi di 300$.
  3. Crea un nuovo progetto o scegli di riutilizzarne uno esistente.

4. Apri editor di Cloud Shell

  1. Vai all'editor di Cloud Shell.
  2. Se il terminale non viene visualizzato nella parte inferiore dello schermo, aprilo:
    • Fai clic sul menu a tre linee Icona del menu di navigazione.
    • Fai clic su Terminale.
    • Fai clic su Nuovo terminaleApri un nuovo terminale nell'editor di Cloud Shell.
  3. Nel terminale, imposta il tuo progetto con questo comando:
    • Formato:
      gcloud config set project [PROJECT_ID]
      
    • Esempio:
      gcloud config set project lab-project-id-example
      
    • Se non ricordi l'ID progetto:
      • Puoi elencare tutti gli ID progetto con:
        gcloud projects list | awk '/PROJECT_ID/{print $2}'
        
      Imposta l'ID progetto nel terminale dell'editor di Cloud Shell
  4. Se ti viene richiesto di concedere l'autorizzazione, fai clic su Autorizza per continuare. Fai clic per autorizzare Cloud Shell
  5. Dovresti visualizzare questo messaggio:
    Updated property [core/project].
    
    Se visualizzi un WARNING e ti viene chiesto Do you want to continue (Y/N)?, probabilmente hai inserito l'ID progetto in modo errato. Premi N, premi Enter e prova a eseguire di nuovo il comando gcloud config set project.

5. Abilita API

Nel terminale, abilita le API:

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

Se ti viene richiesto di concedere l'autorizzazione, fai clic su Autorizza per continuare. Fai clic per autorizzare Cloud Shell

Il completamento di questo comando potrebbe richiedere alcuni minuti, ma alla fine dovrebbe essere visualizzato un messaggio di operazione riuscita simile a questo:

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

6. Crea un database Firestore

  1. Esegui il comando gcloud firestore databases create per creare un database Firestore
    gcloud firestore databases create --location=nam5
    

7. Prepara l'applicazione

Prepara un'applicazione Next.js che risponda alle richieste HTTP.

  1. Per creare un nuovo progetto Next.js denominato task-app, utilizza il comando:
    npx --yes create-next-app@15 task-app \
      --ts \
      --eslint \
      --tailwind \
      --no-src-dir \
      --turbopack \
      --app \
      --no-import-alias
    
  2. Passa alla directory task-app:
    cd task-app
    
  1. Installa firebase-admin per interagire con il database Firestore.
    npm install firebase-admin
    
  1. Apri il file actions.ts nell'editor di Cloud Shell:
    cloudshell edit app/actions.ts
    
    Ora nella parte superiore dello schermo dovrebbe essere visualizzato un file vuoto. Qui puoi modificare questo file actions.ts. Mostra che il codice va inserito nella sezione superiore dello schermo
  2. Copia il seguente codice e incollalo nel file actions.ts aperto:
    '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. Apri il file page.tsx nell'editor di Cloud Shell:
    cloudshell edit app/page.tsx
    
    Ora dovrebbe essere visualizzato un file esistente nella parte superiore dello schermo. Qui puoi modificare questo file page.tsx. Mostra che il codice va inserito nella sezione superiore dello schermo
  2. Elimina i contenuti esistenti del file page.tsx.
  3. Copia il seguente codice e incollalo nel file page.tsx aperto:
    '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>
      );
    }
    

L'applicazione è ora pronta per il deployment.

8. Esegui il deployment dell'applicazione in Cloud Run

  1. Esegui il comando riportato di seguito per eseguire il deployment dell'applicazione su Cloud Run:
    gcloud run deploy helloworld \
      --region=us-central1 \
      --source=.
    
  2. Se richiesto, premi Y e Enter per confermare che vuoi continuare:
    Do you want to continue (Y/n)? Y
    

Dopo qualche minuto, l'applicazione dovrebbe fornire un URL da visitare.

Vai all'URL per vedere la tua applicazione in azione. Ogni volta che visiti l'URL o aggiorni la pagina, vedrai l'app Attività.

9. Complimenti

In questo lab hai imparato a:

  • Crea un'istanza Cloud SQL per PostgreSQL
  • Esegui il deployment di un'applicazione su Cloud Run che si connette al tuo database Cloud SQL

Esegui la pulizia

Cloud SQL non ha un livello senza costi e ti addebiterà un costo se continui a utilizzarlo. Puoi eliminare il tuo progetto Cloud per evitare addebiti aggiuntivi.

Sebbene non siano previsti addebiti per Cloud Run quando il servizio non è in uso, ti potrebbero comunque essere addebitati i costi di archiviazione dell'immagine container in Artifact Registry. L'eliminazione del progetto Cloud interrompe la fatturazione per tutte le risorse utilizzate al suo interno.

Se vuoi, elimina il progetto:

gcloud projects delete $GOOGLE_CLOUD_PROJECT

Potresti anche voler eliminare le risorse non necessarie dal disco Cloud Shell. Puoi:

  1. Elimina la directory del progetto codelab:
    rm -rf ~/task-app
    
  2. Attenzione. La prossima azione non può essere annullata. Se vuoi eliminare tutti i contenuti di Cloud Shell per liberare spazio, puoi eliminare l'intera home directory. Fai attenzione che tutto ciò che vuoi conservare sia salvato altrove.
    sudo rm -rf $HOME
    

Continua a imparare