全文検索

ほとんどのアプリでは、ユーザーがアプリのコンテンツを検索できるようになっています。たとえば、特定の単語を含む投稿や、特定のトピックについて書いたメモを検索する、といったケースが考えられます。

Cloud Firestore では、ネイティブ インデックスの作成やドキュメント内のテキスト フィールドの検索をサポートしていません。さらに、コレクション全体をダウンロードして、クライアント側でフィールドを検索することは現実的ではありません。

ソリューション: Algolia

Cloud Firestore データの全文検索を有効にするには、Algolia のようなサードパーティの検索サービスを使用します。各メモがドキュメントとして記録されているメモ作成アプリを見てみましょう。

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

Algolia と Cloud Functions を使用して、各メモのコンテンツのインデックスを登録し、検索を有効にすることができます。まず、アプリ ID と API キーを使用して Algolia クライアントを設定します。

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);

次に、メモが書き込まれるたびにインデックスを更新する関数を追加します。

Node.js

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

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

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

データのインデックスが作成されたら、iOS、Android、またはウェブのいずれかとの Algolia インテグレーションを使用して、データを検索できます。

ウェブ

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

// Search query
const query = 'Some text';

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

セキュリティの追加

元のソリューションは、すべてのメモをすべてのユーザーに検索させても問題ない場合にはうまく機能します。しかし、多くのユースケースでは出力する結果を安全に制限する必要があります。これらのユースケースでは、保護された API キーを生成する HTTP Cloud Functions の関数を追加して、ユーザーが実行するすべてのクエリに特定のフィルタを追加することができます。

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);

この関数を使用して Algolia の検索キーを提供する場合、ユーザーが検索できるメモは author フィールドがユーザー ID と正確に一致しているもののみに制限されます。

ウェブ

const projectID = 'YOUR_PROJECT_ID';

// Search query
const query = 'Some text';

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

        // Perform the search as usual.
        return index.search({query});
    }).then(responses => {
        console.log(responses.hits);
    });

クエリごとに検索キーをフェッチする必要はありません。検索速度を上げるには、取得したキーまたは Algolia クライアントをキャッシュする必要があります。

制限事項

上記のソリューションは Cloud Firestore データに全文検索を簡単に追加できる方法です。ただし、サードパーティの検索プロバイダは Algolia だけではないことを認識しておいてください。ソリューションを本番環境にデプロイする前に、Elasticsearch などの別の検索プロバイダについても検討してみましょう。

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。