Ir para o console
Teste o Cloud Firestore: conheça o banco de dados escalonável e flexível do Firebase e do Google Cloud Platform. Saiba mais sobre o Cloud Firestore.

Ampliar o Realtime Database com o Cloud Functions

Com o Cloud Functions você pode processar eventos no Firebase Realtime Database sem necessidade de atualizar o código do cliente. Além disso, também é possível executar operações de banco de dados com todos os privilégios de administrador e garantir o processamento individual de cada alteração. É possível fazer alterações no Firebase Realtime Database por meio do DataSnapshot ou do SDK Admin.

Em um ciclo de vida comum, uma função do Firebase Realtime Database realiza as seguintes funções:

  1. Espera por alterações em um local de banco de dados específico.
  2. É acionada quando um evento ocorre e realiza as respectivas tarefas (consulte O que posso fazer com o Cloud Functions? para ver exemplos de casos de uso).
  3. Recebe um objeto de dados que contém um instantâneo dos dados armazenados no documento especificado.

Acionar uma função de banco de dados

Crie novas funções para eventos do Realtime Database com o functions.database. Para controlar quando a função é acionada, especifique um dos manipuladores de eventos e o caminho do banco de dados no qual ela detectará os eventos.

Definir o manipulador de eventos

O Functions permite processar eventos do banco de dados em dois níveis de especificidade. É possível detectar especificamente apenas eventos de criação, atualização ou exclusão ou detectar qualquer alteração de qualquer tipo em um caminho. O Cloud Functions é compatível com os seguintes manipuladores de eventos do Realtime Database:

  • onWrite(), que é acionado quando os dados são criados, atualizados ou excluídos no Realtime Database.
  • onCreate() que é acionado quando novos dados são criados no Realtime Database.
  • onUpdate(), que é acionado quando os dados são atualizados no Realtime Database.
  • onDelete(), que é acionado quando os dados são excluídos do Realtime Database.

Especificar a instância e o caminho do banco de dados

Para controlar quando e onde sua função será acionada, chame ref(path) para especificar um caminho. Como opção, especifique uma instância de banco de dados com instance('INSTANCE_NAME'). Se você não especificar uma instância, a função será implantada na instância padrão do banco de dados para o projeto do Firebase. Por exemplo:

  • Instância de banco de dados padrão: functions.database.ref('/foo/bar')
  • Instância denominada "my-app-db-2": functions.database.instance('my-app-db-2').ref('/foo/bar')

Esses métodos direcionam sua função para processar gravações em um determinado caminho na instância do banco de dados. As especificações de caminho correspondem a todas as gravações realizadas em um caminho, incluindo as que ocorrem em qualquer lugar abaixo dele. Se você definir o caminho para sua função como /foo/bar, ele corresponderá a gravações nestes dois locais:

 /foo/bar
 /foo/bar/baz/really/deep/path

Em ambos os casos, o Firebase interpreta que o evento ocorre em /foo/bar, e os dados do evento incluem os dados antigos e novos em /foo/bar. Se os dados do evento forem grandes, considere usar várias funções em caminhos mais profundos em vez de uma única função próxima à raiz do banco de dados. Para ter o melhor desempenho, solicite apenas dados no nível mais profundo possível.

Para especificar um componente do caminho como curinga, basta colocá-lo entre chaves. ref('foo/{bar}') corresponde a qualquer filho de /foo. Os valores desses componentes de caminho de caracteres curinga estão disponíveis no objeto EventContext.params da sua função. Neste exemplo, o valor está disponível como event.params.bar.

Os caminhos com caracteres curinga podem corresponder a vários eventos a partir de uma única gravação. Uma inserção de

{
  "foo": {
    "hello": "world",
    "firebase": "functions"
  }
}

corresponde ao caminho "/foo/{bar}" duas vezes: em "hello": "world" e depois em "firebase": "functions".

Processar dados de eventos

Ao lidar com um evento do Realtime Database, o objeto de dados retornado é um DataSnapshot. Para eventos onWrite ou onUpdate, o primeiro parâmetro é um objeto Change que contém dois instantâneos que representam o estado de dados antes e depois do evento de acionamento. Para eventos onCreate e onDelete, o objeto de dados retornado é um instantâneo dos dados criados ou excluídos.

Neste exemplo, a função recupera o instantâneo do caminho especificado como snap, converte a string nessa localização para maiúscula e grava essa string modificada no banco de dados:

// Listens for new messages added to /messages/:pushId/original and creates an
// uppercase version of the message to /messages/:pushId/uppercase
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snapshot, context) => {
      // Grab the current value of what was written to the Realtime Database.
      const original = snapshot.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return snapshot.ref.parent.child('uppercase').set(uppercase);
    });

Acessar informações de autenticação do usuário

É possível acessar as informações do usuário por meio de EventContext.auth e EventContext.authType, também é possível acessar as permissões para o usuário que acionou uma função. Isso pode ser útil para aplicar regras de segurança, permitindo que sua função conclua operações diferentes com base no nível de permissões do usuário:

const functions = require('firebase-functions');
const admin = require('firebase-admin');

exports.simpleDbFunction = functions.database.ref('/path')
    .onCreate((snap, context) => {
      if (context.authType === 'ADMIN') {
        // do something
      } else if (context.authType === 'USER') {
        console.log(snap.val(), 'written by', context.auth.uid);
      }
    });

Além disso, você pode aproveitar as informações de autenticação do usuário para "representar" um usuário e executar operações de gravação em nome dele. Exclua a instância do aplicativo conforme mostrado abaixo para evitar problemas de simultaneidade:

exports.impersonateMakeUpperCase = functions.database.ref('/messages/{pushId}/original')
    .onCreate((snap, context) => {
      const appOptions = JSON.parse(process.env.FIREBASE_CONFIG);
      appOptions.databaseAuthVariableOverride = context.auth;
      const app = admin.initializeApp(appOptions, 'app');
      const uppercase = snap.val().toUpperCase();
      const ref = snap.ref.parent.child('uppercase');

      const deleteApp = () => app.delete().catch(() => null);

      return app.database().ref(ref).set(uppercase).then(res => {
        // Deleting the app is necessary for preventing concurrency leaks
        return deleteApp().then(() => res);
      }).catch(err => {
        return deleteApp().then(() => Promise.reject(err));
      });
    });

Como ler o valor anterior

O objeto Change tem uma propriedade before que permite inspecionar o que foi salvo no banco de dados antes do evento. A propriedade before retorna um DataSnapshot em que todos os métodos (por exemplo, val() e exists()) se referem ao valor anterior. Para ler o novo valor novamente, use o DataSnapshot original ou leia a propriedade after. Essa propriedade em qualquer Change é outro DataSnapshot que representa o estado dos dados depois que o evento aconteceu.

Por exemplo, a propriedade before pode ser usada para garantir que a função só transcreva texto em caixa alta quando ele é criado pela primeira vez:

exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
    .onWrite((change, context) => {
      // Only edit data when it is first created.
      if (change.before.exists()) {
        return null;
      }
      // Exit when the data is deleted.
      if (!change.after.exists()) {
        return null;
      }
      // Grab the current value of what was written to the Realtime Database.
      const original = change.after.val();
      console.log('Uppercasing', context.params.pushId, original);
      const uppercase = original.toUpperCase();
      // You must return a Promise when performing asynchronous tasks inside a Functions such as
      // writing to the Firebase Realtime Database.
      // Setting an "uppercase" sibling in the Realtime Database returns a Promise.
      return change.after.ref.parent.child('uppercase').set(uppercase);
    });