ほとんどのアプリでは、ユーザーがアプリのコンテンツを検索できるようになっています。たとえば、特定の単語を含む投稿や、特定のトピックについて書いたメモを検索する、といったケースが考えられます。
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 クライアントを設定します。
// 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);
次に、メモが書き込まれるたびにインデックスを更新する関数を追加します。
// 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);
});
データのインデックスが作成されたら、iOS、Android、またはウェブのいずれかとの Algolia インテグレーションを使用して、データを検索できます。
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);
});
セキュリティの追加
元のソリューションは、すべてのメモをすべてのユーザーに検索させても問題ない場合にはうまく機能します。しかし、多くのユースケースでは出力する結果を安全に制限する必要があります。これらのユースケースでは、保護された API キーを生成する HTTP Cloud Functions の関数を追加して、ユーザーが実行するすべてのクエリに特定のフィルタを追加することができます。
// 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 と正確に一致しているもののみに制限されます。
// 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;
});
クエリごとに検索キーをフェッチする必要はありません。検索速度を上げるには、取得したキーまたは Algolia クライアントをキャッシュする必要があります。
制限事項
上記のソリューションは Cloud Firestore データに全文検索を簡単に追加できる方法です。ただし、サードパーティの検索プロバイダは Algolia だけではないことを認識しておいてください。ソリューションを本番環境にデプロイする前に、Elasticsearch などの別の検索プロバイダについても検討してみましょう。