Apollo ServerからFireStoreのデータを取得する
概要
この記事は前回の記事の続きになります。
前回はCloud Functions for Firebaseを使ってApollo Serverを構築しました。今回は、FireStoreのデータをGraphQLから取得するところまでやります。
サンプルは以下のリポジトリにまとめてます。
データの準備
サンプルデータはインターネットムービーデータベースを使用します。 GraphQL Schemaは以下のように定義しました。
type Movie { title: String year: Int info: MovieInfo } type MovieInfo { directors: [String] release_date: String rating: Float genres: [String] image_url: String plot: String rank: Int running_time_secs: Int actors: [String] }
Movie型がタイトルと公開年と MovieInfo型を持っています。MovieInfoには詳細な映画情報が入力されています。 上記サンプルデータをFireStoreに追加します。とりあえず追加したかったので愚直にFor文を回す実装をしました。
import * as admin from "firebase-admin"; import MovieData from "./moviedata.json"; admin.initializeApp(); const db = admin.firestore(); MovieData.forEach(async movie => { await db.collection("movies").add(movie); });
Firebaseのコンソールからデータを確認できれば完了です。
クエリスキーマ
データが準備できたので、SchemaのクエリとFireStreを接続してみます。今回、Schemaのクエリは以下の二つを実装します。
type MovieListPayload { movies: [Movie] lastKey: String } type Query { popularMovies: [Movie] movieList(lastKey: String): MovieListPayload }
popularMovies
クエリは映画の順位から上位10個のデータを返すクエリです。movieList
は映画一覧を10件ずつ返し、次の10件を取得するためのキー( lastKey
)を一緒に返します。
それぞれリゾルバーを実装してみます。
映画上位ランキング一覧
ランキング順に並べ替えたいので FireStore の orderBy
を info.rank
フィールドに対して実行します。その後 limit
をかければ上位10位以内の映画一覧を取得できます。
Apollo ServerのリゾルバーはSchemaのフィールド名とTypeScript内のオブジェクトキー名が一致している必要があります。(今回は popularMovies
)
リゾルバーを実装すると以下になります。
const resolvers = { Query: { popularMovies: async () => { const snapshot = await db .collection("movies") .orderBy("info.rank") .limit(10) .get(); return snapshot.docs.map(doc => { return doc.data(); }); } } }
デプロイしてクエリを実行すると以下のようになりました。
ランキング順に取得できています。(1位が欠損しているのが気になりますが。)
映画一覧
映画一覧はソートするキーを映画公開日にします。一回のクエリでは10件固定で取得でき、簡易的なページネーションを実装して、次の10件を取得できるようにします。 (ここでは任意のページや任意の映画数をクライアントから指定できる実装はしません。)
クエリ引数として lastKey
を受け取れますが、nullableのためFireStoreへのクエリに条件分岐を追加します。
オブジェクトへの変換処理は映画上位ランキング一覧と同じです。
movieList: async (_: any, args: MovieListInput) => { let snapshot; const query = db .collection("movies") .orderBy("info.release_date", "desc"); if (args.lastKey) { snapshot = await query .startAfter(args.lastKey) .limit(10) .get(); } else { snapshot = await query.limit(10).get(); } const movies = snapshot.docs.map(doc => { return doc.data(); }); }
レスポンスにも lastKey
を含める必要があります。レスポンスの場合は取得した映画一覧が空かどうかで lastKey
をnullにするかを決めます。
let lastKey: string | null = null; if (movies.length) { lastKey = movies[movies.length - 1].info.release_date; } return { movies, lastKey };
デプロイして動作確認してみます。クエリ引数を空にして実行すると以下のようになりました。
映画一覧を公開順に取得できています。取得できた lastKey
を基に再実行してみます。
1回目とは異なるデータを取得できました。このままだと映画の公開日が一緒の場合、データが欠損してしまうので今後修正していきたいと思います。