Один из лучших способов улучшить взаимодействие с пользователем в вашем приложении - обеспечить полнотекстовый поиск по множеству списков или коллекций.

В этом руководстве я расскажу, как реализовать полнотекстовый поиск в реальном времени с помощью функций Algolia, Firebase и Firestore (новая база данных Firebase в реальном времени).

Вы можете подумать, подождите - вы только что сказали, что Firestore - это база данных в реальном времени - зачем мне нужна еще одна база данных в реальном времени для обработки полнотекстового поиска. Хороший вопрос! Ответ: Firestore отлично подходит для обновления изменений в коллекции по мере их появления, поскольку он настраивает слушателей для любой коллекции или отдельного объекта, который вам нравится. Однако Firestore не предназначен для предоставления сверхбыстрых предварительно проиндексированных результатов поиска с использованием произвольного ввода текста. Тем не менее, в этом и сияет Алголия.

Алголия - это поиск как услуга. Короче говоря, это дает вашему приложению результаты быстрого полнотекстового поиска Google. Все, что вы добавляете в Algolia, можно искать (даже с орфографическими ошибками, как я показал в видео выше).

В целом, шаги по внедрению поиска Algolia:

  1. Создайте учетную запись Algolia algolia.com
  2. Создайте индекс в Algolia (это просто означает, каково имя базы данных, которая уже существует в вашем приложении, в которой вы хотите выполнить поиск - в моей базе данных Firestore у меня была коллекция «оборудования», поэтому я создал индекс «оборудования» в Algolia.
  3. Создайте функцию firebase, которая отслеживает создание и обновления в вашей коллекции, и скопируйте их в Algolia.
  4. Добавьте несколько элементов в коллекцию Firestore, чтобы проверить ее
  5. Вызовите Algolia в интерфейсе вашего приложения, чтобы отобразить результаты поиска.

Давайте пройдемся по каждому из этих шагов, не так ли?

Шаг 1. Создайте учетную запись Algolia

Здесь больше нечего сказать, просто зайдите на algolia.com и создайте бесплатную учетную запись

Шаг 2. Создайте индекс в Algolia

Создав учетную запись, нажмите "Индексы" на левой панели, а затем нажмите "Новый индекс".

Назовите этот индекс как хотите, но для простоты я оставляю имя индекса таким же, как имя моей коллекции Firestore.

Шаг 3. Создайте функции Firebase, которые прослушивают создание и редактирование коллекции, в которой вы хотите выполнить поиск

В моем случае я хочу знать, когда будет добавлено или отредактировано новое оборудование. Для этого я написал следующие функции firebase.

Прослушайте действия CREATE и EDIT над коллекцией «оборудования» в Firestore:

// this the firebase functions setup code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
let Promise = require('promise');
const cors = require('cors')({ origin: true });
const auth = require('basic-auth');
const request = require('request');
const algoliasearch = require('algoliasearch');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();

// listen for creating a piece of equipment in Firestore
exports.addEquipmentToAlgolia = functions.firestore.document('equipment/{document}')
.onCreate(event => {
console.log('ADD EQUIP EVENT IS', event);
const active = event.data.data().active === true ? "true" : "false"
const data = {
  objectID: event.params.document,
  description: event.data.data().description, 
  category: event.data.data().category,
  category_id: event.data.data().category_id,
  flat_fee: event.data.data().flat_fee,
  group: event.data.data().group,
  hourly: event.data.data().hourly,
  active: active,
  daily: event.data.data().daily,
  weekly: event.data.data().weekly,
  monthly: event.data.data().monthly,
  bulkItem: event.data.data().bulkItem 
 };
return addToAlgolia(data, 'equipment')
 .then(res => console.log('SUCCESS ALGOLIA equipment ADD', res))
 .catch(err => console.log('ERROR ALGOLIA equipment ADD', err));
});
// listen for editing a piece of equipment in Firestore
exports.editEquipmentToAlgolia = functions.firestore.document('equipment/{document}')
.onUpdate(event => {
console.log('edit event', event.data.data())
const active = event.data.data().active === true ? "true" : "false"
const data = {
  objectID: event.params.document,
  description: event.data.data().description, 
  category: event.data.data().category,
  category_id: event.data.data().category_id,
  flat_fee: event.data.data().flat_fee,
  group: event.data.data().group,
  hourly: event.data.data().hourly,
  active: active,
  bundleItem: event.data.data().bundleItem,
  daily: event.data.data().daily,
  weekly: event.data.data().weekly,
  monthly: event.data.data().monthly,
  bulkItem: event.data.data().bulkItem 
 };
console.log('DATA in is', data)
return editToAlgolia(data, 'equipment')
 .then(res => console.log('SUCCESS ALGOLIA EQUIPMENT EDIT', res))
 .catch(err => console.log('ERROR ALGOLIA EQUIPMENT EDIT', err));
});
// listen for a delete of a piece of equipment in Firestore
exports.removeEquipmentFromAlgolia = functions.firestore.document('equipment/{document}')
.onDelete(event => {
 const objectID = event.params.document;
 return removeFromAlgolia(objectID, 'equipment')
 .then(res => console.log('SUCCESS ALGOLIA equipment ADD', res))
 .catch(err => console.log('ERROR ALGOLIA equipment ADD', err));
})
// helper functions for create, edit and delete in Firestore to replicate this in Algolia
function addToAlgolia(object, indexName) {
 console.log('GETS IN addToAlgolia')
 console.log('object', object)
 console.log('indexName', indexName)
 const ALGOLIA_ID = functions.config().algolia.app_id;
 const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
 const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
 const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
  index.addObject(object)
  .then(res => { console.log('res GOOD', res); resolve(res) })
  .catch(err => { console.log('err BAD', err); reject(err) });
 });
}
function editToAlgolia(object, indexName) {
 const ALGOLIA_ID = functions.config().algolia.app_id;
 const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
 const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
 const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
  index.saveObject(object)
  .then(res => { console.log('res GOOD', res); resolve(res) })
  .catch(err => { console.log('err BAD', err); reject(err) });
 });
}
function removeFromAlgolia(objectID, indexName) {
 const ALGOLIA_ID = functions.config().algolia.app_id;
 const ALGOLIA_ADMIN_KEY = functions.config().algolia.api_key;
 const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
 const index = client.initIndex(indexName);
return new Promise((resolve, reject) => {
  index.deleteObject(objectID)
  .then(res => { console.log('res GOOD', res); resolve(res) })
  .catch(err => { console.log('err BAD', err); reject(err) });
 });
}

Приведенный выше код находится в вашем файле functions / index.js в вашем приложении firebase.

Важно: вы заметите, что есть ссылки на functions.config (). algolia.app_id - мы настраиваем это в командной строке следующим образом:

Внутри вашего приложения в терминале выполните следующие команды (заменив YOUR_ALGOLIA_APP_ID на идентификатор приложения в Algolia и YOUR_ADMIN_API_KEY на Admin API Key в Algolia.

$ firebase functions:config:set algolia.app_id="YOUR_ALGOLIA_APP_ID" algolia.api_key="YOUR_ADMIN_API_KEY"

Чтобы найти свои ключи, перейдите к API-ключам на левой панели в Algolia и скопируйте их.

После того, как вы добавили ключи в Firebase, теперь вы сможете получить доступ к ключам для создания, обновления и удаления объектов индекса в Algolia.

Также важно: в верхней части приведенного выше фрагмента кода вы увидите, что мы используем модуль npm algoliasearch. Нам нужно будет установить это локально для развертывания наших функций, так что давайте сделаем это сейчас. Из корня вашего приложения в терминале:

$ cd functions
$ yarn add algoliasearch

(Чтобы код работал, как показано выше, убедитесь, что вы установили и другие показанные пакеты)

Шаг 4. Добавьте несколько элементов в коллекцию Firestore, чтобы проверить ее.

Давайте проверим, что наша функция теперь работает. Сначала нам нужно развернуть нашу функцию:

$ firebase deploy --only functions

Затем перейдите в раздел Firestore в firebase и вручную добавьте элемент в свою коллекцию, соответствующий полезной нагрузке, которую он ожидает в функции, которую вы только что написали выше.

После того, как вы добавили этот новый элемент в свою коллекцию Firestore, перейдите в раздел «Функции» Firebase, чтобы просмотреть журналы и убедиться в успешном выполнении на стороне Firebase (затем мы пойдем посмотреть в Algolia).

Вы можете увидеть что-то подобное, показывающее "Успех" в добавленном нами console.log. Я открыл объект DATA, чтобы проверить только что добавленный элемент коллекции.

Теперь давайте проверим Algolia, чтобы убедиться, что этот элемент тоже там есть.

Если повезет, наш предмет экипировки теперь также скопирован в Algolia:

Теперь у нас это работает (и, если предположить, что наши функции обновления и удаления также работают, мы готовы искать во внешнем интерфейсе - тис!

Шаг 5. Позвоните в Algolia во внешнем интерфейсе приложения, чтобы отобразить результаты поиска

Во-первых, давайте добавим тег скрипта, который предоставит нам доступ к клиентским методам algolia. В index.html добавьте:

<script src="https://cdn.jsdelivr.net/algoliasearch/3/algoliasearch.min.js"></script>

Интерфейс этого конкретного приложения был написан на Angular 1, поэтому, пожалуйста, не обращайте внимания на «vm». и синтаксис «$ scope» на данный момент, поскольку функция во многом идентична для React и т. д.

// search algolia for equipment name
  function doSearch() {
    $scope.results = [];
const client = algoliasearch('Application ID', 'Search-Only API Key');
    index = client.initIndex('equipment');
vm.newQuery = { query: vm.querySearch };
index.search(vm.newQuery)
    .then(res => {
      if (vm.querySearch.length === 0) {
        $scope.results = []
        vm.currentFilter.description = '';
        vm.currentFilter.category = '';
        vm.showDropdown = false;
      } else {
        lodash.map(res.hits, item => item.name = item.description);
        $scope.results = res.hits;
        vm.currentFilter.category = '';
        vm.showDropdown = true;
      }
      $scope.$apply();
    })
    .catch(err => console.error(err));
}

Выше мы вызываем метод algoliasearch (доступный из тега сценария, добавленного выше этого фрагмента), который принимает наш идентификатор приложения, использованный ранее. Ключ API только для поиска можно найти в разделе ключей API в Algolia над ключом API администратора - важно использовать этот ключ и не раскрывать здесь свой ключ API администратора.

Алголия - сверхмощный инструмент. Мы даже не коснулись того, как улучшить рейтинг результатов в зависимости от вашей отрасли, требований клиентов или любых других факторов, которые мы могли бы добавить - возможно, для будущего руководства.

Отличная работа! Мы все настроены. Теперь вы должны иметь возможность вызывать базу данных Algolia, которая является действующей и самообновляющейся репликой вашей коллекции Firestore. А у ваших пользователей теперь в вашем приложении есть быстрый поиск Google!

Если у вас есть вопросы по Algolia, Firebase Functions или Firestore, я буду рад помочь.

Бен :)