Codelab Web di Cloud Firestore

1. Panoramica

Obiettivi

In questo codelab creerai un'app web per consigliare ristoranti basata su Cloud Firestore .

img5.png

Cosa imparerai

  • Leggi e scrivi dati su Cloud Firestore da un'app Web
  • Ascolta le modifiche ai dati Cloud Firestore in tempo reale
  • Utilizza l'autenticazione Firebase e le regole di sicurezza per proteggere i dati Cloud Firestore
  • Scrivi query Cloud Firestore complesse

Di cosa avrai bisogno

Prima di iniziare questo codelab, assicurati di aver installato:

2. Crea e configura un progetto Firebase

Crea un progetto Firebase

  1. Nella console Firebase , fai clic su Aggiungi progetto , quindi denomina il progetto Firebase FriendlyEats .

Ricorda l'ID progetto per il tuo progetto Firebase.

  1. Fare clic su Crea progetto .

L'applicazione che costruiremo utilizza alcuni servizi Firebase disponibili sul web:

  • Autenticazione Firebase per identificare facilmente i tuoi utenti
  • Cloud Firestore per salvare i dati strutturati sul Cloud e ricevere una notifica istantanea quando i dati vengono aggiornati
  • Firebase Hosting per ospitare e servire le tue risorse statiche

Per questo codelab specifico, abbiamo già configurato Firebase Hosting. Tuttavia, per Firebase Auth e Cloud Firestore, ti guideremo attraverso la configurazione e l'abilitazione dei servizi utilizzando la console Firebase.

Abilita autenticazione anonima

Sebbene l'autenticazione non sia il focus di questo codelab, è importante avere una qualche forma di autenticazione nella nostra app. Utilizzeremo l'accesso anonimo , il che significa che l'utente accederà silenziosamente senza che gli venga richiesto.

Dovrai abilitare l'accesso anonimo.

  1. Nella console Firebase, individua la sezione Build nel menu di navigazione sinistro.
  2. Fai clic su Autenticazione , quindi fai clic sulla scheda Metodo di accesso (o fai clic qui per andare direttamente lì).
  3. Abilita il provider di accesso anonimo , quindi fai clic su Salva .

img7.png

Ciò consentirà all'applicazione di accedere silenziosamente ai tuoi utenti quando accedono all'app Web. Sentiti libero di leggere la documentazione sull'autenticazione anonima per saperne di più.

Abilita Cloud Firestore

L'app utilizza Cloud Firestore per salvare e ricevere informazioni e valutazioni sui ristoranti.

Dovrai abilitare Cloud Firestore. Nella sezione Compila della console Firebase, fai clic su Database Firestore . Fai clic su Crea database nel riquadro Cloud Firestore.

L'accesso ai dati in Cloud Firestore è controllato dalle regole di sicurezza. Parleremo più approfonditamente delle regole più avanti in questo codelab, ma prima dobbiamo impostare alcune regole di base sui nostri dati per iniziare. Nella scheda Regole della console Firebase aggiungi le seguenti regole e quindi fai clic su Pubblica .

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      //
      // WARNING: These rules are insecure! We will replace them with
      // more secure rules later in the codelab
      //
      allow read, write: if request.auth != null;
    }
  }
}

Le regole precedenti limitano l'accesso ai dati agli utenti che hanno effettuato l'accesso, impedendo agli utenti non autenticati di leggere o scrivere. Questo è meglio che consentire l'accesso pubblico ma è ancora lontano dall'essere sicuro, miglioreremo queste regole più avanti nel codelab.

3. Ottieni il codice di esempio

Clona il repository GitHub dalla riga di comando:

git clone https://github.com/firebase/friendlyeats-web

Il codice di esempio dovrebbe essere stato clonato nella directory 📁 friendlyeats-web . D'ora in poi, assicurati di eseguire tutti i comandi da questa directory:

cd friendlyeats-web/vanilla-js

Importa l'app iniziale

Utilizzando il tuo IDE (WebStorm, Atom, Sublime, Visual Studio Code...) apri o importa la directory 📁 friendlyeats-web . Questa directory contiene il codice iniziale per il codelab che consiste in un'app di consigli sui ristoranti non ancora funzionante. Lo renderemo funzionale durante questo codelab, quindi presto dovrai modificare il codice in quella directory.

4. Installa l'interfaccia della riga di comando di Firebase

L'interfaccia a riga di comando (CLI) di Firebase ti consente di servire la tua app Web localmente e di distribuirla su Firebase Hosting.

  1. Installa la CLI eseguendo il seguente comando npm:
npm -g install firebase-tools
  1. Verificare che la CLI sia stata installata correttamente eseguendo il comando seguente:
firebase --version

Assicurati che la versione della CLI Firebase sia v7.4.0 o successiva.

  1. Autorizza la CLI Firebase eseguendo il comando seguente:
firebase login

Abbiamo configurato il modello di app Web per estrarre la configurazione della tua app per Firebase Hosting dalla directory e dai file locali della tua app. Ma per fare ciò, dobbiamo associare la tua app al tuo progetto Firebase.

  1. Assicurati che la riga di comando acceda alla directory locale della tua app.
  2. Associa la tua app al tuo progetto Firebase eseguendo il comando seguente:
firebase use --add
  1. Quando richiesto, seleziona il tuo ID progetto , quindi assegna un alias al tuo progetto Firebase.

Un alias è utile se disponi di più ambienti (produzione, staging, ecc.). Tuttavia, per questo codelab, utilizziamo semplicemente l'alias di default .

  1. Segui le restanti istruzioni nella riga di comando.

5. Eseguire il server locale

Siamo pronti per iniziare effettivamente a lavorare sulla nostra app! Eseguiamo la nostra app localmente!

  1. Esegui il seguente comando CLI di Firebase:
firebase emulators:start --only hosting
  1. La riga di comando dovrebbe visualizzare la seguente risposta:
hosting: Local server: http://localhost:5000

Stiamo utilizzando l'emulatore Firebase Hosting per servire la nostra app localmente. L'app Web dovrebbe ora essere disponibile da http://localhost:5000 .

  1. Apri la tua app all'indirizzo http://localhost:5000 .

Dovresti vedere la tua copia di FriendlyEats che è stata collegata al tuo progetto Firebase.

L'app si è connessa automaticamente al tuo progetto Firebase e ti ha effettuato l'accesso silenziosamente come utente anonimo.

img2.png

6. Scrivi i dati su Cloud Firestore

In questa sezione scriveremo alcuni dati su Cloud Firestore in modo da poter popolare l'interfaccia utente dell'app. Questa operazione può essere eseguita manualmente tramite la console Firebase , ma lo faremo nell'app stessa per dimostrare una scrittura di base su Cloud Firestore.

Modello di dati

I dati Firestore sono suddivisi in raccolte, documenti, campi e sottoraccolte. Memorizzeremo ogni ristorante come documento in una raccolta di primo livello chiamata restaurants .

img3.png

Successivamente, memorizzeremo ciascuna recensione in una sottoraccolta denominata ratings sotto ciascun ristorante.

img4.png

Aggiungi ristoranti a Firestore

L'oggetto modello principale nella nostra app è un ristorante. Scriviamo del codice che aggiunge un documento ristorante alla raccolta restaurants .

  1. Dai file scaricati, apri scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.addRestaurant .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.addRestaurant = function(data) {
  var collection = firebase.firestore().collection('restaurants');
  return collection.add(data);
};

Il codice sopra aggiunge un nuovo documento alla raccolta restaurants . I dati del documento provengono da un semplice oggetto JavaScript. A tale scopo, otteniamo prima un riferimento a una raccolta di restaurants Cloud Firestore, quindi add i dati.

Aggiungiamo ristoranti!

  1. Torna all'app FriendlyEats nel browser e aggiornala.
  2. Fare clic su Aggiungi dati fittizi .

L'app genererà automaticamente un set casuale di oggetti ristoranti, quindi chiamerà la funzione addRestaurant . Tuttavia, non vedrai ancora i dati nella tua app Web effettiva perché dobbiamo ancora implementare il recupero dei dati (la sezione successiva del codelab).

Se accedi alla scheda Cloud Firestore nella console Firebase, ora dovresti vedere i nuovi documenti nella raccolta restaurants !

img6.png

Congratulazioni, hai appena scritto dati su Cloud Firestore da un'app Web!

Nella sezione successiva imparerai come recuperare i dati da Cloud Firestore e visualizzarli nella tua app.

7. Visualizza i dati da Cloud Firestore

In questa sezione imparerai come recuperare i dati da Cloud Firestore e visualizzarli nella tua app. I due passaggi chiave sono la creazione di una query e l'aggiunta di un listener di snapshot. Questo ascoltatore riceverà una notifica di tutti i dati esistenti che corrispondono alla query e riceverà aggiornamenti in tempo reale.

Innanzitutto, costruiamo la query che servirà l'elenco predefinito e non filtrato di ristoranti.

  1. Torna al file scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.getAllRestaurants .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.getAllRestaurants = function(renderer) {
  var query = firebase.firestore()
      .collection('restaurants')
      .orderBy('avgRating', 'desc')
      .limit(50);

  this.getDocumentsInQuery(query, renderer);
};

Nel codice sopra, costruiamo una query che recupererà fino a 50 ristoranti dalla raccolta di livello superiore denominata restaurants , ordinati in base alla valutazione media (attualmente tutti zero). Dopo aver dichiarato questa query, la passiamo al metodo getDocumentsInQuery() che è responsabile del caricamento e del rendering dei dati.

Lo faremo aggiungendo un ascoltatore di istantanee.

  1. Torna al file scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.getDocumentsInQuery .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.getDocumentsInQuery = function(query, renderer) {
  query.onSnapshot(function(snapshot) {
    if (!snapshot.size) return renderer.empty(); // Display "There are no restaurants".

    snapshot.docChanges().forEach(function(change) {
      if (change.type === 'removed') {
        renderer.remove(change.doc);
      } else {
        renderer.display(change.doc);
      }
    });
  });
};

Nel codice precedente, query.onSnapshot attiverà la richiamata ogni volta che viene apportata una modifica al risultato della query.

  • La prima volta, la richiamata viene attivata con l'intero set di risultati della query, ovvero l'intera raccolta restaurants da Cloud Firestore. Quindi passa tutti i singoli documenti alla funzione renderer.display .
  • Quando un documento viene eliminato, change.type equivale a removed . Quindi in questo caso chiameremo una funzione che rimuove il ristorante dall'interfaccia utente.

Ora che abbiamo implementato entrambi i metodi, aggiorna l'app e verifica che i ristoranti che abbiamo visto in precedenza nella console Firebase siano ora visibili nell'app. Se hai completato correttamente questa sezione, la tua app ora sta leggendo e scrivendo dati con Cloud Firestore!

Man mano che l'elenco dei ristoranti cambia, questo ascoltatore continuerà ad aggiornarsi automaticamente. Prova ad accedere alla console Firebase ed eliminare manualmente un ristorante o modificarne il nome: vedrai immediatamente le modifiche apparire sul tuo sito!

img5.png

8. Ottieni() dati

Finora abbiamo mostrato come utilizzare onSnapshot per recuperare gli aggiornamenti in tempo reale; tuttavia, non è sempre ciò che vogliamo. A volte ha più senso recuperare i dati solo una volta.

Vorremo implementare un metodo che venga attivato quando un utente fa clic su un ristorante specifico nella tua app.

  1. Torna al file scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.getRestaurant .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.getRestaurant = function(id) {
  return firebase.firestore().collection('restaurants').doc(id).get();
};

Dopo aver implementato questo metodo, sarai in grado di visualizzare le pagine di ciascun ristorante. Basta fare clic su un ristorante nell'elenco e dovresti vedere la pagina dei dettagli del ristorante:

img1.png

Per ora, non puoi aggiungere valutazioni poiché dovremo ancora implementare l'aggiunta di valutazioni in un secondo momento nel codelab.

9. Ordina e filtra i dati

Attualmente, la nostra app mostra un elenco di ristoranti, ma l'utente non ha modo di filtrarli in base alle proprie esigenze. In questa sezione utilizzerai le query avanzate di Cloud Firestore per abilitare il filtro.

Ecco un esempio di una semplice query per recuperare tutti i ristoranti Dim Sum :

var filteredQuery = query.where('category', '==', 'Dim Sum')

Come suggerisce il nome, il metodo where() farà sì che la nostra query scarichi solo i membri della raccolta i cui campi soddisfano le restrizioni che abbiamo impostato. In questo caso, verranno scaricati solo i ristoranti la cui category è Dim Sum .

Nella nostra app, l'utente può concatenare più filtri per creare query specifiche, come "Pizza a San Francisco" o "Pesce a Los Angeles ordinati per popolarità".

Creeremo un metodo che crea una query che filtrerà i nostri ristoranti in base a più criteri selezionati dai nostri utenti.

  1. Torna al file scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.getFilteredRestaurants .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.getFilteredRestaurants = function(filters, renderer) {
  var query = firebase.firestore().collection('restaurants');

  if (filters.category !== 'Any') {
    query = query.where('category', '==', filters.category);
  }

  if (filters.city !== 'Any') {
    query = query.where('city', '==', filters.city);
  }

  if (filters.price !== 'Any') {
    query = query.where('price', '==', filters.price.length);
  }

  if (filters.sort === 'Rating') {
    query = query.orderBy('avgRating', 'desc');
  } else if (filters.sort === 'Reviews') {
    query = query.orderBy('numRatings', 'desc');
  }

  this.getDocumentsInQuery(query, renderer);
};

Il codice precedente aggiunge più filtri where e una singola clausola orderBy per creare una query composta basata sull'input dell'utente. La nostra query ora restituirà solo i ristoranti che soddisfano i requisiti dell'utente.

Aggiorna la tua app FriendlyEats nel browser, quindi verifica di poter filtrare per prezzo, città e categoria. Durante il test, vedrai errori nella console JavaScript del tuo browser simili a questi:

The query requires an index. You can create it here: https://console.firebase.google.com/project/project-id/database/firestore/indexes?create_composite=...

Questi errori sono dovuti al fatto che Cloud Firestore richiede indici per la maggior parte delle query composte. La richiesta di indici sulle query mantiene Cloud Firestore veloce e su larga scala.

L'apertura del collegamento dal messaggio di errore aprirà automaticamente l'interfaccia utente di creazione dell'indice nella console Firebase con i parametri corretti compilati. Nella sezione successiva, scriveremo e distribuiremo gli indici necessari per questa applicazione.

10. Distribuire gli indici

Se non vogliamo esplorare ogni percorso nella nostra app e seguire ciascuno dei collegamenti per la creazione dell'indice, possiamo facilmente distribuire più indici contemporaneamente utilizzando la CLI di Firebase.

  1. Nella directory locale scaricata della tua app troverai un file firestore.indexes.json .

Questo file descrive tutti gli indici necessari per tutte le possibili combinazioni di filtri.

firestore.indexes.json

{
 "indexes": [
   {
     "collectionGroup": "restaurants",
     "queryScope": "COLLECTION",
     "fields": [
       { "fieldPath": "city", "order": "ASCENDING" },
       { "fieldPath": "avgRating", "order": "DESCENDING" }
     ]
   },

   ...

 ]
}
  1. Distribuisci questi indici con il seguente comando:
firebase deploy --only firestore:indexes

Dopo alcuni minuti, i tuoi indici saranno attivi e i messaggi di errore scompariranno.

11. Scrivere i dati in una transazione

In questa sezione aggiungeremo la possibilità per gli utenti di inviare recensioni ai ristoranti. Finora, tutte le nostre scritture sono state atomiche e relativamente semplici. Se qualcuno di essi presentasse errori, probabilmente chiederemo semplicemente all'utente di riprovare oppure la nostra app riproverebbe la scrittura automaticamente.

La nostra app avrà molti utenti che vorranno aggiungere una valutazione per un ristorante, quindi dovremo coordinare più letture e scritture. Prima è necessario inviare la recensione stessa, quindi è necessario aggiornare il count delle valutazioni del ristorante e average rating . Se uno di questi fallisce ma non l'altro, ci ritroviamo in uno stato incoerente in cui i dati in una parte del nostro database non corrispondono ai dati in un'altra.

Fortunatamente, Cloud Firestore fornisce funzionalità di transazione che ci consentono di eseguire più letture e scritture in un'unica operazione atomica, garantendo che i nostri dati rimangano coerenti.

  1. Torna al file scripts/FriendlyEats.Data.js .
  2. Trova la funzione FriendlyEats.prototype.addRating .
  3. Sostituisci l'intera funzione con il seguente codice.

FriendlyEats.Data.js

FriendlyEats.prototype.addRating = function(restaurantID, rating) {
  var collection = firebase.firestore().collection('restaurants');
  var document = collection.doc(restaurantID);
  var newRatingDocument = document.collection('ratings').doc();

  return firebase.firestore().runTransaction(function(transaction) {
    return transaction.get(document).then(function(doc) {
      var data = doc.data();

      var newAverage =
          (data.numRatings * data.avgRating + rating.rating) /
          (data.numRatings + 1);

      transaction.update(document, {
        numRatings: data.numRatings + 1,
        avgRating: newAverage
      });
      return transaction.set(newRatingDocument, rating);
    });
  });
};

Nel blocco sopra, attiviamo una transazione per aggiornare i valori numerici di avgRating e numRatings nel documento del ristorante. Allo stesso tempo, aggiungiamo la nuova rating alla sottoraccolta ratings .

12. Proteggi i tuoi dati

All'inizio di questo codelab, impostiamo le regole di sicurezza della nostra app per aprire completamente il database a qualsiasi lettura o scrittura. In un'applicazione reale, vorremmo impostare regole molto più precise per impedire l'accesso o la modifica indesiderata dei dati.

  1. Nella sezione Compila della console Firebase, fai clic su Database Firestore .
  2. Fai clic sulla scheda Regole nella sezione Cloud Firestore (o fai clic qui per andare direttamente lì).
  3. Sostituisci le impostazioni predefinite con le seguenti regole, quindi fai clic su Pubblica .

firestore.rules

rules_version = '2';
service cloud.firestore {

  // Determine if the value of the field "key" is the same
  // before and after the request.
  function unchanged(key) {
    return (key in resource.data) 
      && (key in request.resource.data) 
      && (resource.data[key] == request.resource.data[key]);
  }

  match /databases/{database}/documents {
    // Restaurants:
    //   - Authenticated user can read
    //   - Authenticated user can create/update (for demo purposes only)
    //   - Updates are allowed if no fields are added and name is unchanged
    //   - Deletes are not allowed (default)
    match /restaurants/{restaurantId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null;
      allow update: if request.auth != null
                    && (request.resource.data.keys() == resource.data.keys()) 
                    && unchanged("name");
      
      // Ratings:
      //   - Authenticated user can read
      //   - Authenticated user can create if userId matches
      //   - Deletes and updates are not allowed (default)
      match /ratings/{ratingId} {
        allow read: if request.auth != null;
        allow create: if request.auth != null
                      && request.resource.data.userId == request.auth.uid;
      }
    }
  }
}

Queste regole limitano l'accesso per garantire che i client apportino solo modifiche sicure. Per esempio:

  • Gli aggiornamenti al documento di un ristorante possono modificare solo le valutazioni, non il nome o altri dati immutabili.
  • Le valutazioni possono essere create solo se l'ID utente corrisponde all'utente che ha effettuato l'accesso, il che impedisce lo spoofing.

In alternativa alla console Firebase, puoi utilizzare la CLI Firebase per distribuire regole al tuo progetto Firebase. Il file firestore.rules nella tua directory di lavoro contiene già le regole di cui sopra. Per distribuire queste regole dal tuo file system locale (anziché utilizzare la console Firebase), esegui il comando seguente:

firebase deploy --only firestore:rules

13. Conclusione

In questo codelab hai imparato come eseguire operazioni di lettura e scrittura di base e avanzate con Cloud Firestore, nonché come proteggere l'accesso ai dati con regole di sicurezza. Puoi trovare la soluzione completa nel repository quickstarts-js .

Per ulteriori informazioni su Cloud Firestore, visita le seguenti risorse:

14. [Facoltativo] Applica con App Check

Firebase App Check fornisce protezione aiutando a convalidare e prevenire il traffico indesiderato verso la tua app. In questo passaggio, assicurerai l'accesso ai tuoi servizi aggiungendo App Check con reCAPTCHA Enterprise .

Innanzitutto, dovrai abilitare App Check e reCaptcha.

Abilitazione di reCaptcha Enterprise

  1. Nella console Cloud, trova e seleziona reCaptcha Enterprise in Sicurezza.
  2. Abilita il servizio come richiesto e fai clic su Crea chiave .
  3. Inserisci un nome visualizzato come richiesto e seleziona Sito web come tipo di piattaforma.
  4. Aggiungi gli URL distribuiti all'elenco dei domini e assicurati che l'opzione "Utilizza verifica casella di controllo" sia deselezionata .
  5. Fare clic su Crea chiave e archiviare la chiave generata da qualche parte per tenerla al sicuro. Ne avrai bisogno più avanti in questo passaggio.

Abilitazione del controllo dell'app

  1. Nella console Firebase, individua la sezione Build nel pannello di sinistra.
  2. Fai clic su App Check , quindi fai clic sul pulsante Inizia (o reindirizza direttamente alla console ).
  3. Fai clic su Registra e inserisci la chiave reCaptcha Enterprise quando richiesto, quindi fai clic su Salva .
  4. Nella visualizzazione API, seleziona Archiviazione e fai clic su Applica . Fai lo stesso per Cloud Firestore .

Ora il controllo dell'app dovrebbe essere applicato! Aggiorna la tua app e prova a creare/visualizzare un ristorante. Dovresti ricevere il messaggio di errore:

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.

Ciò significa che App Check blocca per impostazione predefinita le richieste non convalidate. Ora aggiungiamo la convalida alla tua app.

Passa al file FriendlyEats.View.js , aggiorna la funzione initAppCheck e aggiungi la chiave reCaptcha per inizializzare App Check.

FriendlyEats.prototype.initAppCheck = function() {
    var appCheck = firebase.appCheck();
    appCheck.activate(
    new firebase.appCheck.ReCaptchaEnterpriseProvider(
      /* reCAPTCHA Enterprise site key */
    ),
    true // Set to true to allow auto-refresh.
  );
};

L'istanza appCheck viene inizializzata con un ReCaptchaEnterpriseProvider con la tua chiave e isTokenAutoRefreshEnabled consente l'aggiornamento automatico dei token nella tua app.

Per abilitare il test locale, trova la sezione in cui l'app è inizializzata nel file FriendlyEats.js e aggiungi la seguente riga alla funzione FriendlyEats.prototype.initAppCheck :

if(isLocalhost) {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

Verrà registrato un token di debug nella console dell'app Web locale simile a:

App Check debug token: 8DBDF614-649D-4D22-B0A3-6D489412838B. You will need to add it to your app's App Check settings in the Firebase console for it to work.

Ora vai alla Visualizzazione app di App Check nella console Firebase.

Fai clic sul menu extra e seleziona Gestisci token di debug .

Quindi, fai clic su Aggiungi token di debug e incolla il token di debug dalla console come richiesto.

Congratulazioni! App Check ora dovrebbe funzionare nella tua app.