1. Panoramica
Obiettivi
In questo codelab, creerai un'app web di consigli sui ristoranti basata su Cloud Firestore.
Cosa imparerai a fare
- Leggere e scrivere dati in Cloud Firestore da un'app web
- Ascolta le modifiche ai dati di Cloud Firestore in tempo reale
- Utilizzare Firebase Authentication e le regole di sicurezza per proteggere i dati di Cloud Firestore
- Scrivere query complesse di Cloud Firestore
Che cosa ti serve
Prima di iniziare questo codelab, assicurati di aver installato:
2. Crea e configura un progetto Firebase
Crea un progetto Firebase
- Nella console Firebase, fai clic su Aggiungi progetto, quindi assegna al progetto Firebase il nome FriendlyEats.
Ricorda l'ID progetto per il tuo progetto Firebase.
- Fai clic su Crea progetto.
L'applicazione che stiamo per creare utilizza alcuni servizi Firebase disponibili sul web:
- Firebase Authentication per identificare facilmente gli utenti
- Cloud Firestore per salvare i dati strutturati sul cloud e ricevere una notifica immediata quando i dati vengono aggiornati
- Firebase Hosting per ospitare e pubblicare gli asset statici
Per questo specifico codelab, abbiamo già configurato Firebase Hosting. Tuttavia, per Firebase Auth e Cloud Firestore, ti guideremo nella configurazione e nell'attivazione dei servizi utilizzando la Console Firebase.
Attiva l'autenticazione anonima
Sebbene l'autenticazione non sia l'obiettivo di questo codelab, è importante avere una qualche forma di autenticazione nella nostra app. Utilizzeremo l'accesso anonimo, il che significa che l'utente accederà senza essere richiesto.
Dovrai attivare l'accesso anonimo.
- Nella console Firebase, individua la sezione Build nel riquadro di navigazione a sinistra.
- Fai clic su Authentication (Autenticazione), quindi sulla scheda Sign-in method (Metodo di accesso) (o fai clic qui per andare direttamente a questa pagina).
- Attiva il provider di accesso Anonimo e poi fai clic su Salva.
In questo modo, l'applicazione potrà far accedere gli utenti in modo silenzioso quando accedono all'app web. Per saperne di più, consulta la documentazione sull'autenticazione anonima.
Attiva Cloud Firestore
L'app utilizza Cloud Firestore per salvare e ricevere informazioni e valutazioni dei ristoranti.
Devi attivare Cloud Firestore. Nella sezione Build 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 di più delle regole più avanti in questo codelab, ma per iniziare dobbiamo impostare alcune regole di base sui dati. Nella scheda Regole della Console Firebase, aggiungi le seguenti regole e poi fai clic su Pubblica.
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; } } } }
Discuteremo di queste regole e del loro funzionamento più avanti nel codelab.
3. recupera il codice campione
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
Importare l'app iniziale
Utilizzando il tuo IDE (WebStorm, Atom, Sublime, Visual Studio Code e così via), apri o importa la directory 📁friendlyeats-web
. Questa directory contiene il codice iniziale del codelab, costituito da un'app di consigli sui ristoranti non ancora funzionale. La renderemo funzionale nel corso di questo codelab, quindi dovrai modificare il codice in questa directory a breve.
4. Installa l'interfaccia a riga di comando di Firebase
L'interfaccia a riga di comando (CLI) di Firebase ti consente di pubblicare l'app web localmente e di eseguirne il deployment su Firebase Hosting.
- Installa l'interfaccia a riga di comando eseguendo il seguente comando npm:
npm -g install firebase-tools
- Verifica che la CLI sia stata installata correttamente eseguendo il seguente comando:
firebase --version
Assicurati che la versione dell'interfaccia a riga di comando di Firebase sia 7.4.0 o successiva.
- Autorizza l'interfaccia a riga di comando di Firebase eseguendo il seguente comando:
firebase login
Abbiamo configurato il modello di app web in modo che estragga la configurazione dell'app per Firebase Hosting dalla directory e dai file locali dell'app. Per farlo, però, dobbiamo associare la tua app al tuo progetto Firebase.
- Assicurati che la riga di comando acceda alla directory locale dell'app.
- Associa la tua app al progetto Firebase eseguendo il seguente comando:
firebase use --add
- Quando richiesto, seleziona l'ID progetto e poi assegna un alias al progetto Firebase.
Un alias è utile se hai più ambienti (di produzione, di gestione temporanea e così via). Tuttavia, per questo codelab, utilizziamo solo l'alias di default
.
- Segui le istruzioni rimanenti nella riga di comando.
5. Esegui il server locale
È tutto pronto per iniziare a lavorare alla nostra app. Eseguiamo l'app localmente.
- Esegui il seguente comando Firebase CLI:
firebase emulators:start --only hosting
- La riga di comando dovrebbe mostrare la seguente risposta:
hosting: Local server: http://localhost:5000
Utilizziamo l'emulatore Firebase Hosting per pubblicare la nostra app localmente. L'app web dovrebbe ora essere disponibile all'indirizzo http://localhost:5000.
- Apri l'app all'indirizzo http://localhost:5000.
Dovresti vedere la tua copia di FriendlyEats collegata al tuo progetto Firebase.
L'app si è collegata automaticamente al tuo progetto Firebase e ti ha fatto accedere in silenzio come utente anonimo.
6. Scrivere dati in Cloud Firestore
In questa sezione scriveremo alcuni dati in Cloud Firestore per poter compilare l'interfaccia utente dell'app. Questa operazione può essere eseguita manualmente tramite la Console Firebase, ma la eseguiremo nell'app stessa per dimostrare una scrittura di Cloud Firestore di base.
Modello dati
I dati di Firestore sono suddivisi in raccolte, documenti, campi e sottoraccolte. Archivieremo ogni ristorante come documento in una raccolta di primo livello denominata restaurants
.
In un secondo momento, memorizzeremo ogni recensione in una sottoraccolta denominata ratings
sotto ogni ristorante.
Aggiungere ristoranti a Firestore
L'oggetto modello principale nella nostra app è un ristorante. Scriviamo del codice che aggiunge un documento del ristorante alla raccolta restaurants
.
- Apri
scripts/FriendlyEats.Data.js
dai file scaricati. - Trova la funzione
FriendlyEats.prototype.addRestaurant
. - 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 riportato sopra aggiunge un nuovo documento alla raccolta restaurants
. I dati del documento provengono da un normale oggetto JavaScript. Per farlo, otteniamo prima un riferimento a una raccolta Cloud Firestore restaurants
e poi add
i dati.
Aggiungiamo i ristoranti.
- Torna all'app FriendlyEats nel browser e aggiornala.
- Fai clic su Aggiungi dati simulati.
L'app genererà automaticamente un insieme casuale di oggetti ristorante, 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).
Tuttavia, se vai alla scheda Cloud Firestore nella Console Firebase, dovresti vedere i nuovi documenti nella raccolta restaurants
.
Complimenti, hai appena scritto dati in Cloud Firestore da un'app web.
Nella sezione successiva, imparerai a recuperare i dati da Cloud Firestore e a visualizzarli nella tua app.
7. Visualizzare i dati di Cloud Firestore
In questa sezione imparerai a recuperare i dati da Cloud Firestore e a visualizzarli nella tua app. I due passaggi chiave sono la creazione di una query e l'aggiunta di un ascoltatore di istantanee. Questo ascoltatore riceverà una notifica di tutti i dati esistenti corrispondenti alla query e riceverà aggiornamenti in tempo reale.
Innanzitutto, costruiamo la query che restituirà l'elenco predefinito dei ristoranti non filtrati.
- Torna al file
scripts/FriendlyEats.Data.js
. - Trova la funzione
FriendlyEats.prototype.getAllRestaurants
. - 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 riportato sopra, viene creata una query che recupera fino a 50 ristoranti dalla raccolta di primo livello denominata restaurants
, ordinati in base alla valutazione media (attualmente tutti pari a zero). Dopo aver dichiarato questa query, la passiamo al metodo getDocumentsInQuery()
, che è responsabile del caricamento e del rendering dei dati.
Aggiungeremo un ascoltatore di istantanee.
- Torna al file
scripts/FriendlyEats.Data.js
. - Trova la funzione
FriendlyEats.prototype.getDocumentsInQuery
. - 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 riportato sopra, query.onSnapshot
attiverà il proprio callback ogni volta che il risultato della query cambia.
- La prima volta, il callback viene attivato con l'intero insieme di risultati della query, ovvero l'intera raccolta
restaurants
di Cloud Firestore. Poi passa tutti i singoli documenti alla funzionerenderer.display
. - Quando un documento viene eliminato,
change.type
è uguale aremoved
. 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 questa sezione correttamente, la tua app ora legge e scrive 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 elimina manualmente un ristorante o modificane il nome: vedrai le modifiche sul tuo sito immediatamente.
8. Dati Get()
Finora abbiamo mostrato come utilizzare onSnapshot
per recuperare gli aggiornamenti in tempo reale; tuttavia, non è sempre quello che vogliamo. A volte ha più senso recuperare i dati una sola volta.
Dovremo implementare un metodo che venga attivato quando un utente fa clic su un ristorante specifico nella tua app.
- Torna al file
scripts/FriendlyEats.Data.js
. - Trova la funzione
FriendlyEats.prototype.getRestaurant
. - 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, potrai visualizzare le pagine di ogni ristorante. Basta fare clic su un ristorante nell'elenco per visualizzare la pagina dei dettagli del ristorante:
Per il momento non puoi aggiungere valutazioni perché dobbiamo ancora implementare questa funzionalità nel codelab.
9. Ordinare e filtrare i dati
Al momento, la nostra app mostra un elenco di ristoranti, ma l'utente non può filtrare i risultati in base alle sue esigenze. In questa sezione utilizzerai le query avanzate di Cloud Firestore per attivare i filtri.
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()
consente alla query di scaricare solo gli elementi della raccolta i cui campi soddisfano le limitazioni impostate. In questo caso, verranno scaricati solo i ristoranti in cui category
è Dim Sum
.
Nella nostra app, l'utente può concatenare più filtri per creare query specifiche, ad esempio "Pizza a San Francisco" o "Frutti di mare a Los Angeles ordinati per popolarità".
Creeremo un metodo che genera una query che filtrerà i nostri ristoranti in base a più criteri selezionati dai nostri utenti.
- Torna al file
scripts/FriendlyEats.Data.js
. - Trova la funzione
FriendlyEats.prototype.getFilteredRestaurants
. - 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 riportato sopra aggiunge più filtri where
e una singola clausola orderBy
per creare una query composta in base all'input dell'utente. Ora la nostra query restituirà solo i ristoranti che soddisfano i requisiti dell'utente.
Aggiorna l'app FriendlyEats nel browser, quindi verifica di poter filtrare per prezzo, città e categoria. Durante il test, nella console JavaScript del browser vedrai errori simili al seguente:
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 si verificano perché Cloud Firestore richiede indici per la maggior parte delle query composte. La richiesta di indici per le query mantiene Cloud Firestore veloce su larga scala.
Se apri il link nel messaggio di errore, si aprirà automaticamente l'interfaccia utente per la creazione dell'indice nella console Firebase con i parametri corretti compilati. Nella sezione successiva scriveremo ed eseguiremo il deployment degli indici necessari per questa applicazione.
10. Esegui il deployment degli indici
Se non vogliamo esplorare ogni percorso nella nostra app e seguire ciascuno dei link per la creazione degli indici, possiamo eseguire facilmente il deployment di molti indici contemporaneamente utilizzando Firebase CLI.
- Nella directory locale scaricata dell'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" } ] }, ... ] }
- Esegui il deployment di questi indici con il seguente comando:
firebase deploy --only firestore:indexes
Dopo alcuni minuti, gli indici saranno pubblicati e i messaggi di errore scompariranno.
11. Scrivere 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 uno di questi ha generato un errore, probabilmente chiederemo all'utente di riprovare o la nostra app riproverà automaticamente la scrittura.
La nostra app avrà molti utenti che vogliono aggiungere una valutazione per un ristorante, quindi dovremo coordinare più letture e scritture. Innanzitutto, la recensione stessa deve essere inviata, quindi le valutazioni count
e average rating
del ristorante devono essere aggiornate. Se uno di questi non va a buon fine, ma non l'altro, rimaniamo in uno stato incoerente in cui i dati di una parte del nostro database non corrispondono a quelli di un'altra.
Fortunatamente, Cloud Firestore offre funzionalità di transazione che ci consentono di eseguire più letture e scritture in un'unica operazione atomica, garantendo la coerenza dei dati.
- Torna al file
scripts/FriendlyEats.Data.js
. - Trova la funzione
FriendlyEats.prototype.addRating
. - 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 precedente, attiviamo una transazione per aggiornare i valori numerici di avgRating
e numRatings
nel documento del ristorante. Allo stesso tempo, aggiungiamo il nuovo rating
alla sottoraccolta ratings
.
12. Proteggi i tuoi dati
All'inizio di questo codelab, abbiamo impostato le regole di sicurezza della nostra app per limitare l'accesso alla nostra applicazione.
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. Ad esempio:
- Gli aggiornamenti di un documento del 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 eseguito l'accesso, il che impedisce lo spoofing.
In alternativa all'utilizzo della Console Firebase, puoi utilizzare l'interfaccia a riga di comando di Firebase per eseguire il deployment delle regole nel tuo progetto Firebase. Il file firestore.rules nella tua directory di lavoro contiene già le regole riportate sopra. Per eseguire il deployment di queste regole dal file system locale (anziché utilizzare la console Firebase), esegui il seguente comando:
firebase deploy --only firestore:rules
13. Conclusione
In questo codelab hai imparato a eseguire letture e scritture di base e avanzate con Cloud Firestore, nonché a proteggere l'accesso ai dati con le regole di sicurezza. Puoi trovare la soluzione completa nel repository quickstarts-js.
Per saperne di più su Cloud Firestore, consulta le seguenti risorse:
14. [Facoltativo] Applicare con App Check
Firebase App Check fornisce protezione contribuendo a convalidare e impedire il traffico indesiderato verso la tua app. In questo passaggio, proteggerai l'accesso ai tuoi servizi aggiungendo App Check con reCAPTCHA Enterprise.
Innanzitutto, devi attivare App Check e reCAPTCHA.
Abilitazione di reCAPTCHA Enterprise
- Nella console Cloud, individua e seleziona reCaptcha Enterprise in Sicurezza.
- Attiva il servizio come richiesto e fai clic su Crea chiave.
- Inserisci un nome visualizzato come richiesto e seleziona Sito web come tipo di piattaforma.
- Aggiungi gli URL di cui è stato eseguito il deployment all'elenco dei domini e assicurati che l'opzione "Utilizza verifica tramite casella di controllo" non sia selezionata.
- Fai clic su Crea chiave e conserva la chiave generata in un luogo sicuro. Ti servirà più avanti in questo passaggio.
Attivare App Check
- Nella console Firebase, individua la sezione Build nel riquadro a sinistra.
- Fai clic su Controllo app, quindi sul pulsante Inizia (oppure vai direttamente alla console).
- Fai clic su Registrati e inserisci la chiave di reCaptcha Enterprise quando richiesto, quindi fai clic su Salva.
- Nella visualizzazione API, seleziona Archiviazione e fai clic su Applica. Fai lo stesso per Cloud Firestore.
App Check dovrebbe essere ora applicato. Aggiorna l'app e prova a creare/visualizzare un ristorante. Dovresti visualizzare il messaggio di errore:
Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Ciò significa che App Check blocca le richieste non convalidate per impostazione predefinita. Ora aggiungi la convalida all'app.
Vai al file FriendlyEats.View.js, aggiorna la funzione initAppCheck
e aggiungi la chiave reCaptcha per inizializzare il controllo app.
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 i test locali, individua la sezione in cui l'app viene 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 della tua 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.
Poi, fai clic su Aggiungi token di debug e incolla il token di debug dalla console quando richiesto.
Complimenti! App Check dovrebbe ora funzionare nella tua app.