Codelab da Web do Cloud Firestore

1. Visão Geral

Metas

Neste codelab, você criará um app da Web de recomendação de restaurantes com tecnologia Cloud Firestore .

img5.png

O que você aprenderá

  • Ler e gravar dados no Cloud Firestore a partir de um aplicativo da Web
  • Ouça as alterações nos dados do Cloud Firestore em tempo real
  • Use autenticação e regras de segurança do Firebase para proteger os dados do Cloud Firestore
  • Escreva consultas complexas do Cloud Firestore

O que você precisará

Antes de iniciar este codelab, verifique se você instalou:

2. Crie e configure um projeto do Firebase

Criar um projeto do Firebase

  1. No console do Firebase , clique em Adicionar projeto e nomeie o projeto do Firebase como FriendlyEats .

Lembre-se do ID do projeto do seu projeto Firebase.

  1. Clique em Criar projeto .

A aplicação que vamos construir usa alguns serviços do Firebase disponíveis na web:

  • Firebase Authentication para identificar facilmente seus usuários
  • Cloud Firestore para salvar dados estruturados na nuvem e obter notificação instantânea quando os dados forem atualizados
  • Firebase Hosting para hospedar e servir seus ativos estáticos

Para este codelab específico, já configuramos o Firebase Hosting. No entanto, para Firebase Auth e Cloud Firestore, orientaremos você na configuração e ativação dos serviços usando o Firebase console.

Ativar autenticação anônima

Embora a autenticação não seja o foco deste codelab, é importante ter alguma forma de autenticação em nosso aplicativo. Usaremos o login anônimo - o que significa que o usuário entrará silenciosamente sem ser solicitado.

Você precisará ativar o login anônimo.

  1. No console do Firebase, localize a seção Build na navegação à esquerda.
  2. Clique em Autenticação e, em seguida, clique na guia Método de login (ou clique aqui para ir diretamente para lá).
  3. Ative o Provedor de login anônimo e clique em Salvar .

img7.png

Isso permitirá que o aplicativo conecte silenciosamente seus usuários quando eles acessarem o aplicativo da web. Sinta-se à vontade para ler a documentação de autenticação anônima para saber mais.

Ativar Cloud Firestore

O aplicativo usa o Cloud Firestore para salvar e receber informações e avaliações do restaurante.

Você precisará ativar o Cloud Firestore. Na seção Build do Firebase console, clique em Firestore Database . Clique em Criar banco de dados no painel Cloud Firestore.

O acesso aos dados no Cloud Firestore é controlado por regras de segurança. Falaremos mais sobre regras mais adiante neste codelab, mas primeiro precisamos definir algumas regras básicas em nossos dados para começar. Na guia Regras do console do Firebase, adicione as seguintes regras e clique em Publicar .

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;
    }
  }
}

As regras acima restringem o acesso aos dados aos usuários que estão conectados, o que impede que usuários não autenticados leiam ou gravem. Isso é melhor do que permitir o acesso público, mas ainda está longe de ser seguro. Melhoraremos essas regras posteriormente no codelab.

3. Obtenha o código de amostra

Clone o repositório GitHub na linha de comando:

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

O código de amostra deveria ter sido clonado no diretório 📁 friendlyeats-web . A partir de agora, certifique-se de executar todos os seus comandos a partir deste diretório:

cd friendlyeats-web

Importar o aplicativo inicial

Usando seu IDE (WebStorm, Atom, Sublime, Visual Studio Code...) abra ou importe o diretório 📁 friendlyeats-web . Este diretório contém o código inicial para o codelab, que consiste em um aplicativo de recomendação de restaurante ainda não funcional. Vamos torná-lo funcional ao longo deste codelab, então você precisará editar o código nesse diretório em breve.

4. Instale a interface de linha de comando do Firebase

A interface de linha de comando (CLI) do Firebase permite que você forneça seu aplicativo da web localmente e implante-o no Firebase Hosting.

  1. Instale a CLI executando o seguinte comando npm:
npm -g install firebase-tools
  1. Verifique se a CLI foi instalada corretamente executando o seguinte comando:
firebase --version

Verifique se a versão do Firebase CLI é v7.4.0 ou posterior.

  1. Autorize a Firebase CLI executando o seguinte comando:
firebase login

Configuramos o modelo de aplicativo da Web para extrair a configuração do seu aplicativo para o Firebase Hosting do diretório local e dos arquivos do seu aplicativo. Mas para fazer isso, precisamos associar seu aplicativo ao seu projeto Firebase.

  1. Certifique-se de que sua linha de comando esteja acessando o diretório local do seu aplicativo.
  2. Associe seu aplicativo ao seu projeto do Firebase executando o seguinte comando:
firebase use --add
  1. Quando solicitado, selecione o ID do projeto e atribua um alias ao projeto do Firebase.

Um alias é útil se você tiver vários ambientes (produção, preparação, etc.). No entanto, para este codelab, vamos usar apenas o alias de default .

  1. Siga as instruções restantes em sua linha de comando.

5. Execute o servidor local

Estamos prontos para realmente começar a trabalhar em nosso aplicativo! Vamos executar nosso aplicativo localmente!

  1. Execute o seguinte comando da Firebase CLI:
firebase emulators:start --only hosting
  1. Sua linha de comando deve exibir a seguinte resposta:
hosting: Local server: http://localhost:5000

Estamos usando o emulador Firebase Hosting para atender nosso aplicativo localmente. O aplicativo da Web agora deve estar disponível em http://localhost:5000 .

  1. Abra seu aplicativo em http://localhost:5000 .

Você deve ver sua cópia do FriendlyEats que foi conectada ao seu projeto Firebase.

O aplicativo se conectou automaticamente ao seu projeto do Firebase e fez login silenciosamente como um usuário anônimo.

img2.png

6. Gravar dados no Cloud Firestore

Nesta seção, gravaremos alguns dados no Cloud Firestore para que possamos preencher a IU do aplicativo. Isso pode ser feito manualmente por meio do console do Firebase , mas faremos isso no próprio aplicativo para demonstrar uma gravação básica do Cloud Firestore.

Modelo de dados

Os dados do Firestore são divididos em coleções, documentos, campos e subcoleções. Armazenaremos cada restaurante como um documento em uma coleção de nível superior chamada restaurants .

img3.png

Mais tarde, armazenaremos cada avaliação em uma subcoleção chamada ratings em cada restaurante.

img4.png

Adicionar restaurantes ao Firestore

O principal objeto modelo em nosso aplicativo é um restaurante. Vamos escrever um código que adicione um documento de restaurante à coleção restaurants .

  1. Nos arquivos baixados, abra scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.addRestaurant .
  3. Substitua toda a função pelo código a seguir.

FriendlyEats.Data.js

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

O código acima adiciona um novo documento à coleção dos restaurants . Os dados do documento vêm de um objeto JavaScript simples. Fazemos isso obtendo primeiro uma referência a uma coleção restaurants do Cloud Firestore e, em seguida, add os dados.

Vamos adicionar restaurantes!

  1. Volte para o aplicativo FriendlyEats em seu navegador e atualize-o.
  2. Clique em Adicionar dados simulados .

O aplicativo gerará automaticamente um conjunto aleatório de objetos de restaurantes e, em seguida, chamará sua função addRestaurant . No entanto, você ainda não verá os dados em seu aplicativo da Web real porque ainda precisamos implementar a recuperação dos dados (a próxima seção do codelab).

Se você navegar até a guia Cloud Firestore no console do Firebase, verá novos documentos na coleção restaurants !

img6.png

Parabéns, você acabou de gravar dados no Cloud Firestore a partir de um aplicativo da web!

Na próxima seção, você aprenderá como recuperar dados do Cloud Firestore e exibi-los em seu aplicativo.

7. Exibir dados do Cloud Firestore

Nesta seção, você aprenderá como recuperar dados do Cloud Firestore e exibi-los em seu aplicativo. As duas etapas principais são criar uma consulta e adicionar um ouvinte de instantâneo. Este listener será notificado de todos os dados existentes que correspondam à consulta e receberá atualizações em tempo real.

Primeiro, vamos construir a consulta que servirá à lista padrão não filtrada de restaurantes.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getAllRestaurants .
  3. Substitua toda a função pelo código a seguir.

FriendlyEats.Data.js

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

  this.getDocumentsInQuery(query, renderer);
};

No código acima, construímos uma consulta que recuperará até 50 restaurantes da coleção de nível superior chamada restaurants , que são ordenados pela classificação média (atualmente todos zero). Após declararmos esta consulta, passamos para o método getDocumentsInQuery() que é responsável por carregar e renderizar os dados.

Faremos isso adicionando um ouvinte de instantâneo.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getDocumentsInQuery .
  3. Substitua toda a função pelo código a seguir.

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);
      }
    });
  });
};

No código acima, query.onSnapshot acionará seu retorno de chamada toda vez que houver uma alteração no resultado da consulta.

  • Na primeira vez, o retorno de chamada é acionado com todo o conjunto de resultados da consulta, ou seja, toda a coleção restaurants do Cloud Firestore. Em seguida, passa todos os documentos individuais para a função renderer.display .
  • Quando um documento é excluído, change.type é igual a removed . Nesse caso, chamaremos uma função que remove o restaurante da IU.

Agora que implementamos os dois métodos, atualize o aplicativo e verifique se os restaurantes que vimos anteriormente no console do Firebase agora estão visíveis no aplicativo. Se você concluiu esta seção com sucesso, seu aplicativo agora está lendo e gravando dados com o Cloud Firestore!

À medida que sua lista de restaurantes muda, esse ouvinte continuará atualizando automaticamente. Tente acessar o console do Firebase e excluir manualmente um restaurante ou alterar seu nome - você verá as alterações aparecerem em seu site imediatamente!

img5.png

8. Obter () dados

Até agora, mostramos como usar onSnapshot para recuperar atualizações em tempo real; no entanto, nem sempre é isso que queremos. Às vezes, faz mais sentido buscar os dados apenas uma vez.

Queremos implementar um método que seja acionado quando um usuário clicar em um restaurante específico em seu aplicativo.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getRestaurant .
  3. Substitua toda a função pelo código a seguir.

FriendlyEats.Data.js

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

Depois de implementar esse método, você poderá visualizar as páginas de cada restaurante. Basta clicar em um restaurante na lista e você verá a página de detalhes do restaurante:

img1.png

Por enquanto, você não pode adicionar classificações porque ainda precisamos implementar a adição de classificações posteriormente no codelab.

9. Classifique e filtre os dados

Atualmente, nosso aplicativo exibe uma lista de restaurantes, mas não há como o usuário filtrar com base em suas necessidades. Nesta seção, você usará a consulta avançada do Cloud Firestore para habilitar a filtragem.

Aqui está um exemplo de uma consulta simples para buscar todos os restaurantes Dim Sum :

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

Como o próprio nome indica, o método where() fará com que nossa consulta baixe apenas membros da coleção cujos campos atendam às restrições que definimos. Nesse caso, ele baixará apenas restaurantes cuja category seja Dim Sum .

Em nosso aplicativo, o usuário pode encadear vários filtros para criar consultas específicas, como "Pizza em São Francisco" ou "Frutos do mar em Los Angeles pedidos por popularidade".

Criaremos um método que cria uma consulta que filtrará nossos restaurantes com base em vários critérios selecionados por nossos usuários.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getFilteredRestaurants .
  3. Substitua toda a função pelo código a seguir.

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);
};

O código acima adiciona vários filtros where e uma única cláusula orderBy para criar uma consulta composta com base na entrada do usuário. Nossa consulta agora retornará apenas restaurantes que correspondam aos requisitos do usuário.

Atualize seu aplicativo FriendlyEats em seu navegador e verifique se você pode filtrar por preço, cidade e categoria. Durante o teste, você verá erros no console JavaScript do seu navegador que se parecem com isto:

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

Esses erros ocorrem porque o Cloud Firestore requer índices para a maioria das consultas compostas. Exigir índices em consultas mantém o Cloud Firestore rápido em escala.

Abrir o link da mensagem de erro abrirá automaticamente a IU de criação de índice no Firebase console com os parâmetros corretos preenchidos. Na próxima seção, escreveremos e implantaremos os índices necessários para este aplicativo.

10. Implantar índices

Se não quisermos explorar todos os caminhos em nosso aplicativo e seguir cada um dos links de criação de índice, podemos implantar facilmente vários índices de uma só vez usando o Firebase CLI.

  1. No diretório local baixado do seu aplicativo, você encontrará um arquivo firestore.indexes.json .

Este arquivo descreve todos os índices necessários para todas as combinações possíveis de filtros.

firestore.indexes.json

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

   ...

 ]
}
  1. Implante esses índices com o seguinte comando:
firebase deploy --only firestore:indexes

Após alguns minutos, seus índices estarão ativos e as mensagens de erro desaparecerão.

11. Gravar dados em uma transação

Nesta seção, adicionaremos a capacidade de os usuários enviarem avaliações a restaurantes. Até agora, todas as nossas gravações foram atômicas e relativamente simples. Se algum deles apresentar erro, provavelmente solicitaremos ao usuário que tente novamente ou nosso aplicativo tentará a gravação novamente automaticamente.

Nosso aplicativo terá muitos usuários que desejam adicionar uma classificação para um restaurante, portanto, precisaremos coordenar várias leituras e gravações. Primeiro, a avaliação em si deve ser enviada e, em seguida, a count de classificação do restaurante e average rating precisam ser atualizadas. Se um deles falhar, mas não o outro, ficamos em um estado inconsistente em que os dados em uma parte do nosso banco de dados não correspondem aos dados em outra.

Felizmente, o Cloud Firestore fornece funcionalidade de transação que nos permite realizar várias leituras e gravações em uma única operação atômica, garantindo que nossos dados permaneçam consistentes.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.addRating .
  3. Substitua toda a função pelo código a seguir.

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);
    });
  });
};

No bloco acima, acionamos uma transação para atualizar os valores numéricos de avgRating e numRatings no documento do restaurante. Ao mesmo tempo, adicionamos a nova rating à subcoleção ratings .

12. Proteja seus dados

No início deste codelab, definimos as regras de segurança do nosso aplicativo para abrir completamente o banco de dados para qualquer leitura ou gravação. Em um aplicativo real, gostaríamos de definir regras muito mais refinadas para impedir acesso ou modificação indesejável de dados.

  1. Na seção Build do Firebase console, clique em Firestore Database .
  2. Clique na guia Regras na seção Cloud Firestore (ou clique aqui para ir diretamente para lá).
  3. Substitua os padrões pelas regras a seguir e clique em Publicar .

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;
      }
    }
  }
}

Essas regras restringem o acesso para garantir que os clientes façam apenas alterações seguras. Por exemplo:

  • As atualizações em um documento de restaurante podem alterar apenas as classificações, não o nome ou qualquer outro dado imutável.
  • As classificações só podem ser criadas se o ID do usuário corresponder ao usuário conectado, o que evita a falsificação.

Como alternativa ao Firebase console, você pode usar a Firebase CLI para implantar regras em seu projeto Firebase. O arquivo firestore.rules em seu diretório de trabalho já contém as regras acima. Para implantar essas regras de seu sistema de arquivos local (em vez de usar o Firebase console), execute o seguinte comando:

firebase deploy --only firestore:rules

13. Conclusão

Neste codelab, você aprendeu a realizar leituras e gravações básicas e avançadas com o Cloud Firestore, além de proteger o acesso aos dados com regras de segurança. Você pode encontrar a solução completa no repositório quickstarts-js .

Para saber mais sobre o Cloud Firestore, visite os seguintes recursos: