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 do Cloud Firestore .

img5.png

O que você aprenderá

  • Ler e gravar dados no Cloud Firestore a partir de um app da Web
  • Ouça as alterações nos dados do Cloud Firestore em tempo real
  • Use o Firebase Authentication e as regras de segurança 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 Firebase

Crie 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 do Firebase.

  1. Clique em Criar projeto .

O aplicativo 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 receber 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 console do Firebase.

Habilitar autenticação anônima

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

Você precisará ativar o login anônimo.

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

img7.png

Isso permitirá que o aplicativo faça login silenciosamente nos usuários quando eles acessarem o aplicativo Web. Sinta-se à vontade para ler a documentação da Autenticação Anônima para saber mais.

Ativar o Cloud Firestore

O aplicativo usa Cloud Firestore para salvar e receber informações e classificações de restaurantes.

Você precisará ativar o Cloud Firestore. Na seção Build do console do Firebase, 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 posteriormente 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 escrevam. 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 exemplo

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 . De agora em diante, certifique-se de executar todos os seus comandos deste diretório:

cd friendlyeats-web/vanilla-js

Importe 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 do codelab, que consiste em um app de recomendação de restaurantes 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

Certifique-se de que a versão do Firebase CLI seja v7.4.0 ou posterior.

  1. Autorize a CLI do Firebase 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 e dos arquivos locais do seu aplicativo. Mas para fazer isso, precisamos associar seu aplicativo ao projeto do Firebase.

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

Um alias é útil se você tiver vários ambientes (produção, teste, etc.). No entanto, para este codelab, vamos usar apenas o alias 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 CLI do Firebase:
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 do Firebase Hosting para servir 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ê deverá 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. Grave 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

Posteriormente, 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 a função inteira 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 restaurants . Os dados do documento vêm de um objeto JavaScript simples. Fazemos isso primeiro obtendo uma referência a uma coleção de restaurants do Cloud Firestore e depois add os dados.

Vamos adicionar restaurantes!

  1. Volte para o aplicativo FriendlyEats no 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 no seu aplicativo da Web real porque ainda precisamos implementar a recuperação dos dados (a próxima seção do codelab).

Porém, se você navegar até a guia Cloud Firestore no console do Firebase, deverá 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 no seu aplicativo.

7. Exibir dados do Cloud Firestore

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

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

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getAllRestaurants .
  3. Substitua a função inteira 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 snapshot.

  1. Volte para o arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getDocumentsInQuery .
  3. Substitua a função inteira 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 irá acionar seu retorno de chamada sempre 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, ele passa todos os documentos individuais para a função renderer.display .
  • Quando um documento é excluído, change.type é igual a removed . Portanto, neste caso, chamaremos uma função que remove o restaurante da UI.

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 êxito, seu aplicativo agora está lendo e gravando dados com o Cloud Firestore!

À medida que sua lista de restaurantes muda, este ouvinte será atualizado 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. Dados Get()

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 clica em um restaurante específico em seu aplicativo.

  1. Volte para o seu arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getRestaurant .
  3. Substitua a função inteira 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, não é possível adicionar classificações, pois ainda precisaremos implementar a adição de classificações posteriormente no codelab.

9. Classifique e filtre 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á as consultas avançadas do Cloud Firestore para ativar a filtragem.

Aqui está um exemplo de 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. Neste caso, só baixará 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 encomendados por popularidade”.

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

  1. Volte para o seu arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.getFilteredRestaurants .
  3. Substitua a função inteira 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 construir uma consulta composta com base na entrada do usuário. Nossa consulta agora retornará apenas restaurantes que atendam 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 parecidos com estes:

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 exige índices para a maioria das consultas compostas. A exigência de índices nas 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 console do Firebase 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 vez usando a CLI do Firebase.

  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. Escreva dados em uma transação

Nesta seção, adicionaremos a capacidade dos usuários enviarem avaliações sobre 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á novamente a gravação automaticamente.

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

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

  1. Volte para o seu arquivo scripts/FriendlyEats.Data.js .
  2. Encontre a função FriendlyEats.prototype.addRating .
  3. Substitua a função inteira 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 app para abrir completamente o banco de dados para qualquer leitura ou gravação. Em uma aplicação real, gostaríamos de definir regras muito mais refinadas para evitar acesso ou modificação indesejável de dados.

  1. Na seção Build do console do Firebase, 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;
      }
    }
  }
}

Estas 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 só podem alterar as classificações, não o nome ou quaisquer outros dados imutáveis.
  • As classificações só poderão ser criadas se o ID do usuário corresponder ao usuário conectado, o que evita falsificação.

Como alternativa ao console do Firebase, você pode usar a CLI do Firebase para implantar regras em seu projeto do Firebase. O arquivo firestore.rules em seu diretório de trabalho já contém as regras acima. Para implantar essas regras do seu sistema de arquivos local (em vez de usar o console do Firebase), você executaria 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:

14. [Opcional] Aplicar com App Check

O Firebase App Check oferece proteção ajudando a validar e evitar tráfego indesejado em seu aplicativo. Nesta etapa, você protegerá o acesso aos seus serviços adicionando o App Check com o reCAPTCHA Enterprise .

Primeiro, você precisará ativar o App Check e o reCaptcha.

Habilitando o reCaptcha Enterprise

  1. No console do Cloud, encontre e selecione reCaptcha Enterprise em Segurança.
  2. Habilite o serviço conforme solicitado e clique em Criar chave .
  3. Insira um nome de exibição conforme solicitado e selecione Site como seu tipo de plataforma.
  4. Adicione seus URLs implantados à lista de domínios e certifique-se de que a opção "Usar desafio de caixa de seleção" esteja desmarcada .
  5. Clique em Criar chave e armazene a chave gerada em algum lugar por segurança. Você precisará dele posteriormente nesta etapa.

Ativando a verificação de aplicativos

  1. No console do Firebase, localize a seção Build no painel esquerdo.
  2. Clique em App Check e, em seguida, clique no botão Começar (ou redirecione diretamente para console ).
  3. Clique em Registrar e insira sua chave reCaptcha Enterprise quando solicitado e clique em Salvar .
  4. Na visualização APIs, selecione Armazenamento e clique em Aplicar . Faça o mesmo para o Cloud Firestore .

O App Check agora deve ser aplicado! Atualize seu aplicativo e tente criar/visualizar um restaurante. Você deve receber a mensagem de erro:

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

Isso significa que o App Check está bloqueando solicitações não validadas por padrão. Agora vamos adicionar validação ao seu aplicativo.

Navegue até o arquivo FriendlyEats.View.js , atualize a função initAppCheck e adicione sua chave reCaptcha para inicializar o 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.
  );
};

A instância appCheck é inicializada com um ReCaptchaEnterpriseProvider com sua chave e isTokenAutoRefreshEnabled permite que os tokens sejam atualizados automaticamente em seu aplicativo.

Para habilitar o teste local, encontre a seção onde o aplicativo é inicializado no arquivo FriendlyEats.js e adicione a seguinte linha à função FriendlyEats.prototype.initAppCheck :

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

Isso registrará um token de depuração no console do seu aplicativo Web local semelhante 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.

Agora, vá para a visualização de aplicativos do App Check no console do Firebase.

Clique no menu flutuante e selecione Gerenciar tokens de depuração .

Em seguida, clique em Adicionar token de depuração e cole o token de depuração do seu console conforme solicitado.

Parabéns! O App Check agora deve estar funcionando em seu aplicativo.