Búsqueda de texto completo

La mayoría de las apps permiten a los usuarios buscar en el contenido de la app. Por ejemplo, es posible que quieras buscar publicaciones que contengan una determinada palabra o notas que escribiste sobre un tema específico.

Cloud Firestore no admite la indexación nativa ni la búsqueda por campos de texto en documentos. Además, descargar una colección completa para buscar campos en el lado del cliente no es práctico.

Solución: Algolia

Para habilitar la búsqueda completa de texto para los datos de Cloud Firestore, usa un servicio de búsqueda externo, como Algolia. Considera el caso de una app para tomar notas en la que cada nota es un documento:

// /notes/${ID}
{
  owner: "{UID}", // Firebase Authentication's User ID of note owner
  text: "This is my first note!"
}

Puedes usar Algolia con Cloud Functions para llenar un índice con el contenido de cada nota y habilitar la búsqueda. En primer lugar, configura un cliente de Algolia con el ID de app y la clave de API:

Node.js

// Initialize Algolia, requires installing Algolia dependencies:
// https://www.algolia.com/doc/api-client/javascript/getting-started/#install
//
// App ID and API Key are stored in functions config variables
const ALGOLIA_ID = functions.config().algolia.app_id;
const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
const ALGOLIA_SEARCH_KEY = functions.config().algolia.search_key;

const ALGOLIA_INDEX_NAME = 'notes';
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);

A continuación, agrega una función que actualice el índice cada vez que se escribe una nota:

Node.js

// Update the search index every time a blog post is written.
exports.onNoteCreated = functions.firestore.document('notes/{noteId}').onCreate((snap, context) => {
  // Get the note document
  const note = snap.data();

  // Add an 'objectID' field which Algolia requires
  note.objectID = context.params.noteId;

  // Write to the algolia index
  const index = client.initIndex(ALGOLIA_INDEX_NAME);
  return index.saveObject(note);
});

Una vez que tus datos están indexados, puedes usar cualquiera de las integraciones de Algolia para iOS, Android o Web y ejecutar una búsqueda de los datos.

Web

var client = algoliasearch(ALGOLIA_APP_ID, ALGOLIA_SEARCH_KEY);
var index = client.initIndex('notes');

// Perform an Algolia search:
// https://www.algolia.com/doc/api-reference/api-methods/search/
index
  .seach({
    query
  })
  .then(function(responses) {
    // Response from Algolia:
    // https://www.algolia.com/doc/api-reference/api-methods/search/#response-format
    console.log(responses.hits);
  });

Agrega seguridad

La solución original funciona bien si todos los usuarios pueden buscar todas las notas. Sin embargo, muchos casos prácticos requieren una limitación segura de los resultados. Para estos casos prácticos, puedes agregar una función HTTP de Cloud Functions que genere una clave de API segura, que agrega filtros específicos a cada consulta que hace un usuario.

Node.js

// This complex HTTP function will be created as an ExpressJS app:
// https://expressjs.com/en/4x/api.html
const app = require('express')();

// We'll enable CORS support to allow the function to be invoked
// from our app client-side.
app.use(require('cors')({origin: true}));

// Then we'll also use a special 'getFirebaseUser' middleware which
// verifies the Authorization header and adds a `user` field to the
// incoming request:
// https://gist.github.com/abehaskins/832d6f8665454d0cd99ef08c229afb42
app.use(getFirebaseUser);

// Add a route handler to the app to generate the secured key
app.get('/', (req, res) => {
  // Create the params object as described in the Algolia documentation:
  // https://www.algolia.com/doc/guides/security/api-keys/#generating-api-keys
  const params = {
    // This filter ensures that only documents where author == user_id will be readable
    filters: `author:${req.user.user_id}`,
    // We also proxy the user_id as a unique token for this key.
    userToken: req.user.user_id,
  };

  // Call the Algolia API to generate a unique key based on our search key
  const key = client.generateSecuredApiKey(ALGOLIA_SEARCH_KEY, params);

  // Then return this key as {key: '...key'}
  res.json({key});
});

// Finally, pass our ExpressJS app to Cloud Functions as a function
// called 'getSearchKey';
exports.getSearchKey = functions.https.onRequest(app);

Si usas esta función para proporcionar la clave de búsqueda de Algolia, el usuario solamente puede buscar notas en las que el campo author sea exactamente igual a su ID de usuario.

Web

// Use Firebase Authentication to request the underlying token
return firebase.auth().currentUser.getIdToken()
  .then(function(token) {
    // The token is then passed to our getSearchKey Cloud Function
    return fetch('https://us-central1-' + PROJECT_ID + '.cloudfunctions.net/getSearchKey/', {
        headers: { Authorization: 'Bearer ' + token }
    });
  })
  .then(function(response) {
    // The Fetch API returns a stream, which we convert into a JSON object.
    return response.json();
  })
  .then(function(data) {
    // Data will contain the restricted key in the `key` field.
    client = algoliasearch(ALGOLIA_APP_ID, data.key);
    index = client.initIndex('notes');

    // Perform the search as usual.
    return index.search({query});
  })
  .then(function(responses) {
    // Finally, use the search 'hits' returned from Algolia.
    return responses.hits;
  });

No es necesario recuperar una clave de búsqueda para cada consulta. Debes almacenar la clave recuperada o el cliente de Algolia en caché para acelerar la búsqueda.

Limitaciones

La solución que se mostró previamente es una manera sencilla de agregar una búsqueda completa de texto a los datos de Cloud Firestore. Sin embargo, ten en cuenta que Algolia no es el único proveedor de búsqueda externo. Considera alternativas como ElasticSearch antes de implementar cualquier solución en fase de producción.

Enviar comentarios sobre…

¿Necesitas ayuda? Visita nuestra página de asistencia.