Amplía Cloud Firestore con Cloud Functions (2nd gen)

Con Cloud Functions, puedes implementar código para administrar eventos que se activan con los cambios en tu base de datos de Cloud Firestore. Esto te permite agregar con facilidad funciones del servidor a tu app, sin ejecutar tus propios servidores.

Cloud Functions (2ª gen.)

Con la tecnología de Cloud Run y Eventarc, Cloud Functions for Firebase (2ª gen.) te brinda una infraestructura más potente, control avanzado sobre el rendimiento y la escalabilidad, y más control del entorno de ejecución de las funciones. Si quieres obtener más información sobre la 2ª gen., consulta Cloud Functions para Firebase (2ª gen.). Para obtener más información sobre la 1ª gen., consulta Amplía Cloud Firestore con Cloud Functions.

Activadores de la función Cloud Firestore

El SDK de Cloud Functions for Firebase exporta los siguientes activadores de eventos de Cloud Firestore que te permiten crear controladores vinculados a eventos específicos de Cloud Firestore:

Node.js

Tipo de evento Activador
onDocumentCreated Se activa cuando se escribe en un documento por primera vez.
onDocumentUpdated Se activa cuando un documento ya existe y se cambia uno de sus valores.
onDocumentDeleted Se activa cuando se borra un documento.
onDocumentWritten Se activa con onDocumentCreated, onDocumentUpdated o onDocumentDeleted.
onDocumentCreatedWithAuthContext onDocumentCreated con información de autenticación adicional
onDocumentWrittenWithAuthContext onDocumentWritten con información de autenticación adicional
onDocumentDeletedWithAuthContext onDocumentDeleted con información de autenticación adicional
onDocumentUpdatedWithAuthContext onDocumentUpdated con información de autenticación adicional

Python (vista previa)

Tipo de evento Activador
on_document_created Se activa cuando se escribe en un documento por primera vez.
on_document_updated Se activa cuando un documento ya existe y se cambia uno de sus valores.
on_document_deleted Se activa cuando se borra un documento.
on_document_written Se activa con on_document_created, on_document_updated o on_document_deleted.
on_document_created_with_auth_context on_document_created con información de autenticación adicional
on_document_updated_with_auth_context on_document_updated con información de autenticación adicional
on_document_deleted_with_auth_context on_document_deleted con información de autenticación adicional
on_document_written_with_auth_context on_document_written con información de autenticación adicional

Solo se activan los eventos Cloud Firestore cuando hay cambios en los documentos. La actualización de un documento de Cloud Firestore en la que no se modifican los datos (una escritura no-op) no genera un evento de actualización ni de escritura. No es posible agregar eventos a campos específicos.

Si aún no tienes un proyecto habilitado para Cloud Functions for Firebase, consulta Comenzar a usar Cloud Functions for Firebase (2ª gen.) para configurar tu proyecto de Cloud Functions for Firebase.

Escribe funciones activadas por Cloud Firestore

Define un activador de función

Para definir un activador de Cloud Firestore, especifica una ruta de documento y un tipo de evento:

Node.js

import {
  onDocumentWritten,
  onDocumentCreated,
  onDocumentUpdated,
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("my-collection/{docId}", (event) => {
   /* ... */ 
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_created,
  on_document_deleted,
  on_document_updated,
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:

Las rutas de acceso de los documentos pueden hacer referencia a un documento específico o un patrón comodín.

Especifica un solo documento

Si deseas activar un evento para cualquier cambio que se realice a un documento específico, puedes usar la siguiente función.

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/marie", (event) => {
  // Your code here
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/marie")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

Especifica un grupo de documentos mediante comodines

Si deseas conectar un activador a un grupo de documentos, como cualquier documento de una colección determinada, utiliza un {wildcard} en lugar del ID del documento:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}", (event) => {
  // If we set `/users/marie` to {name: "Marie"} then
  // event.params.userId == "marie"
  // ... and ...
  // event.data.after.data() == {name: "Marie"}
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie` to {name: "Marie"} then
  event.params["userId"] == "marie"  # True
  # ... and ...
  event.data.after.to_dict() == {"name": "Marie"}  # True

En este ejemplo, cuando se cambia algún campo en cualquier documento de users, coincide con un comodín llamado userId.

Si un documento de users tiene subcolecciones y se modifica un campo en uno de los documentos de las subcolecciones, no se activa el comodín userId.

Las coincidencias de comodines se extraen de la ruta del documento y se almacenan en event.params. Puedes definir tantos comodines como desees para sustituir los IDs explícitos de colección o de documento, por ejemplo:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.myfunction = onDocumentWritten("users/{userId}/{messageCollectionId}/{messageId}", (event) => {
    // If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
    // event.params.userId == "marie";
    // event.params.messageCollectionId == "incoming_messages";
    // event.params.messageId == "134";
    // ... and ...
    // event.data.after.data() == {body: "Hello"}
});

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}/{messageCollectionId}/{messageId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # If we set `/users/marie/incoming_messages/134` to {body: "Hello"} then
  event.params["userId"] == "marie"  # True
  event.params["messageCollectionId"] == "incoming_messages"  # True
  event.params["messageId"] == "134"  # True
  # ... and ...
  event.data.after.to_dict() == {"body": "Hello"}

Los activadores siempre deben apuntar a un documento, incluso si usas un comodín. Por ejemplo, users/{userId}/{messageCollectionId} no es válido porque {messageCollectionId} es una colección. Sin embargo, users/{userId}/{messageCollectionId}/{messageId} es válido porque {messageId} siempre apunta a un documento.

Activadores de eventos

Activa una función cuando se cree un documento nuevo

Puedes activar una función para que se active cada vez que se cree un documento nuevo en una colección. Esta función de ejemplo se activa cada vez que se agrega un nuevo perfil de usuario:

Node.js

import {
  onDocumentCreated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.createuser = onDocumentCreated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snapshot = event.data;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // access a particular field as you would any JS property
    const name = data.name;

    // perform more operations ...
});

Para obtener información de autenticación adicional, usa onDocumentCreatedWithAuthContext.

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_created,
  Event,
  DocumentSnapshot,
)

@on_document_created(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Activa una función cuando se actualice un documento

También puedes activar una función para que se active cuando se actualice un documento. Esta función de ejemplo se activa cada vez que un usuario cambia su perfil:

Node.js

import {
  onDocumentUpdated,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.updateuser = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const newValue = event.data.after.data();

    // access a particular field as you would any JS property
    const name = newValue.name;

    // perform more operations ...
});

Para obtener información de autenticación adicional, usa onDocumentUpdatedWithAuthContext.

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_updated,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get a dictionary representing the document
  # e.g. {'name': 'Marie', 'age': 66}
  new_value = event.data.after.to_dict()

  # Access a particular field as you would any dictionary
  name = new_value["name"]

  # Perform more operations ...

Activa una función cuando se borre un documento

Además, puedes activar una función cuando se borre un documento. Esta función de ejemplo se activa cuando un usuario borra su perfil:

Node.js

import {
  onDocumentDeleted,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.deleteuser = onDocumentDeleted("users/{userId}", (event) => {
    // Get an object representing the document
    // e.g. {'name': 'Marie', 'age': 66}
    const snap =  event.data;
    const data =  snap.data();

    // perform more operations ...
});

Para obtener información de autenticación adicional, usa onDocumentDeletedWithAuthContext.

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_deleted,
  Event,
  DocumentSnapshot,
)

@on_document_deleted(document="users/{userId}")
def myfunction(event: Event[DocumentSnapshot|None]) -> None:
  # Perform more operations ...

Activa una función para todos los cambios en un documento

Si no te interesa el tipo de evento que se activa, puedes detectar todos los cambios en un documento de Cloud Firestore con el activador de eventos “documento escrito”. Esta función de ejemplo se activa si se crea, actualiza o borra un usuario:

Node.js

import {
  onDocumentWritten,
  Change,
  FirestoreEvent
} from "firebase-functions/v2/firestore";

exports.modifyuser = onDocumentWritten("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const document =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();

    // perform more operations ...
});

Para obtener información de autenticación adicional, usa onDocumentWrittenWithAuthContext.

Python (vista previa)

from firebase_functions.firestore_fn import (
  on_document_written,
  Event,
  Change,
  DocumentSnapshot,
)

@on_document_written(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  # Get an object with the current document values.
  # If the document does not exist, it was deleted.
  document = (event.data.after.to_dict()
              if event.data.after is not None else None)

  # Get an object with the previous document values.
  # If the document does not exist, it was newly created.
  previous_values = (event.data.before.to_dict()
                     if event.data.before is not None else None)

  # Perform more operations ...

Lectura y escritura de datos

Cuando se activa una función, esta proporciona una instantánea de los datos relacionados con el evento. Puedes usar esta instantánea para leer el documento que activó el evento o escribir en él, o bien usar el SDK de Firebase Admin para acceder a otras partes de tu base de datos.

Datos de eventos

Lee datos

Cuando se activa una función, es posible que desees obtener datos de un documento antes o después de que se lo actualice. Para obtener los datos previos, puedes usar event.data.before, que contiene la instantánea del documento antes de la actualización. De manera similar, event.data.after contiene el estado de la instantánea del documento después de la actualización.

Node.js

exports.updateuser2 = onDocumentUpdated("users/{userId}", (event) => {
    // Get an object with the current document values.
    // If the document does not exist, it was deleted
    const newValues =  event.data.after.data();

    // Get an object with the previous document values
    const previousValues =  event.data.before.data();
});

Python (vista previa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get an object with the current document values.
  new_value = event.data.after.to_dict()

  # Get an object with the previous document values.
  prev_value = event.data.before.to_dict()

Puedes acceder a las propiedades del mismo modo que con cualquier otro objeto. También puedes utilizar la función get para acceder a campos específicos:

Node.js

// Fetch data using standard accessors
const age = event.data.after.data().age;
const name = event.data.after.data()['name'];

// Fetch data using built in accessor
const experience = event.data.after.data.get('experience');

Python (vista previa)

# Get the value of a single document field.
age = event.data.after.get("age")

# Convert the document to a dictionary.
age = event.data.after.to_dict()["age"]

Escribe datos

Cada invocación de función está asociada a un documento específico de la base de datos de Cloud Firestore. Puedes acceder a ese documento en la instantánea que se muestra a tu función.

La referencia del documento incluye métodos como update(), set() y remove() para que puedas modificar el documento que activó la función.

Node.js

import { onDocumentUpdated } from "firebase-functions/v2/firestore";

exports.countnamechanges = onDocumentUpdated('users/{userId}', (event) => {
  // Retrieve the current and previous value
  const data = event.data.after.data();
  const previousData = event.data.before.data();

  // We'll only update if the name has changed.
  // This is crucial to prevent infinite loops.
  if (data.name == previousData.name) {
    return null;
  }

  // Retrieve the current count of name changes
  let count = data.name_change_count;
  if (!count) {
    count = 0;
  }

  // Then return a promise of a set operation to update the count
  return data.after.ref.set({
    name_change_count: count + 1
  }, {merge: true});

});

Python (vista previa)

@on_document_updated(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:
  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # We'll only update if the name has changed.
  # This is crucial to prevent infinite loops.
  if new_value.get("name") == prev_value.get("name"):
      return

  # Retrieve the current count of name changes
  count = new_value.to_dict().get("name_change_count", 0)

  # Update the count
  new_value.reference.update({"name_change_count": count + 1})

Accede a la información de autenticación del usuario

Si usas uno de los siguientes tipos de eventos, puedes acceder a la información de autenticación del usuario sobre el principal que activó el evento. Esta información se suma a la que se muestra en el evento base.

Node.js

  • onDocumentCreatedWithAuthContext
  • onDocumentWrittenWithAuthContext
  • onDocumentDeletedWithAuthContext
  • onDocumentUpdatedWithAuthContext

Python (vista previa)

  • on_document_created_with_auth_context
  • on_document_updated_with_auth_context
  • on_document_deleted_with_auth_context
  • on_document_written_with_auth_context

Para obtener información sobre los datos disponibles en el contexto de autenticación, consulta Contexto de autenticación. En el siguiente ejemplo, se muestra cómo recuperar información de autenticación:

Node.js

import { onDocumentWrittenWithAuthContext } from "firebase-functions/v2/firestore"

exports.syncUser = onDocumentWrittenWithAuthContext("users/{userId}", (event) => {
    const snapshot = event.data.after;
    if (!snapshot) {
        console.log("No data associated with the event");
        return;
    }
    const data = snapshot.data();

    // retrieve auth context from event
    const { authType, authId } = event;

    let verified = false;
    if (authType === "system") {
      // system-generated users are automatically verified
      verified = true;
    } else if (authType === "unknown" || authType === "unauthenticated") {
      // admin users from a specific domain are verified
      if (authId.endsWith("@example.com")) {
        verified = true;
      }
    }

    return data.after.ref.set({
        created_by: authId,
        verified,
    }, {merge: true}); 
}); 

Python (vista previa)

@on_document_updated_with_auth_context(document="users/{userId}")
def myfunction(event: Event[Change[DocumentSnapshot]]) -> None:

  # Get the current and previous document values.
  new_value = event.data.after
  prev_value = event.data.before

  # Get the auth context from the event
  user_auth_type = event.auth_type
  user_auth_id = event.auth_id

Datos fuera del evento activador

Cloud Functions se ejecuta en entornos de confianza, lo que significa que cuenta con autorización como una cuenta de servicio en tu proyecto. Puedes realizar operaciones de lectura y escritura con el SDK de Firebase Admin:

Node.js

const { initializeApp } = require('firebase-admin/app');
const { getFirestore, Timestamp, FieldValue } = require('firebase-admin/firestore');

initializeApp();
const db = getFirestore();

exports.writetofirestore = onDocumentWritten("some/doc", (event) => {
    db.doc('some/otherdoc').set({ ... });
  });

  exports.writetofirestore = onDocumentWritten('users/{userId}', (event) => {
    db.doc('some/otherdoc').set({
      // Update otherdoc
    });
  });

Python (vista previa)

from firebase_admin import firestore, initialize_app
import google.cloud.firestore

initialize_app()

@on_document_written(document="some/doc")
def myfunction(event: Event[Change[DocumentSnapshot | None]]) -> None:
  firestore_client: google.cloud.firestore.Client = firestore.client()
  firestore_client.document("another/doc").set({
      # ...
  })

Limitaciones

Ten en cuenta las siguientes limitaciones para los activadores de Cloud Firestore de Cloud Functions:

  • Cloud Functions (1ª gen.) es un requisito de una base de datos “(predeterminada)” existente en modo nativo de Firestore. No admite bases de datos con nombre de Cloud Firestore ni modo Datastore. Usa Cloud Functions (2ª gen.) para configurar eventos en esos casos.
  • No se garantiza el ordenamiento. Los cambios rápidos pueden activar invocaciones de funciones en un orden inesperado.
  • Los eventos se entregan al menos una vez, pero un solo evento puede dar lugar a varias invocaciones de funciones. Evita depender de la mecánica de entrega de eventos exactamente una vez y escribe funciones idempotentes.
  • Cloud Firestore en modo Datastore requiere Cloud Functions (2ª gen.). Cloud Functions (1ª gen.) no es compatible con el modo Datastore.
  • Un activador se asocia con una sola base de datos. No puedes crear un activador que coincida con varias bases de datos.
  • Cuando se borra una base de datos, no se borra automáticamente ningún activador de la base de datos. El activador deja de entregar eventos, pero sigue existiendo hasta que lo borras.
  • Si un evento coincidente excede el tamaño máximo de la solicitud, es posible que el evento no se entregue a Cloud Functions (1st gen).
    • Los eventos que no se entregaron debido al tamaño de la solicitud se registran en los registros de la plataforma y se consideran en el uso de registros del proyecto.
    • Puedes encontrar estos registros en el Explorador de registros con el mensaje “El evento no se puede entregar a Cloud Function debido a que el tamaño supera el límite de 1ª gen... de gravedad error”. Puedes encontrar el nombre de la función en el campo functionName. Si el campo receiveTimestamp todavía está dentro de una hora a partir de ahora, puedes inferir el contenido real del evento si lees el documento en cuestión con una instantánea antes y después de la marca de tiempo.
    • Para evitar esa cadencia, puedes hacer lo siguiente:
      • Migra y actualiza a Cloud Functions (2ª gen.)
      • Reducir el tamaño del documento
      • Borra las Cloud Functions en cuestión.
    • Puedes desactivar el registro con exclusiones, pero ten en cuenta que los eventos problemáticos aún no se entregarán.