雑食日誌

Vue.jsとServerless。ときどきチーム開発

Apollo ServerからFireStoreのデータを取得する

概要

この記事は前回の記事の続きになります。

keinumata.hatenablog.com

前回はCloud Functions for Firebaseを使ってApollo Serverを構築しました。今回は、FireStoreのデータをGraphQLから取得するところまでやります。
サンプルは以下のリポジトリにまとめてます。

github.com

データの準備

サンプルデータはインターネットムービーデータベースを使用します。 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のコンソールからデータを確認できれば完了です。

f:id:keinumata:20200104175849p:plain
FireStoreコンソール

クエリスキーマ

データが準備できたので、SchemaのクエリとFireStreを接続してみます。今回、Schemaのクエリは以下の二つを実装します。

type MovieListPayload {
  movies: [Movie]
  lastKey: String
}

type Query {
  popularMovies: [Movie]
  movieList(lastKey: String): MovieListPayload
}

popularMovies クエリは映画の順位から上位10個のデータを返すクエリです。movieList は映画一覧を10件ずつ返し、次の10件を取得するためのキー( lastKey )を一緒に返します。
それぞれリゾルバーを実装してみます。

映画上位ランキング一覧

ランキング順に並べ替えたいので FireStore の orderByinfo.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();
      });
    }
  }
}

デプロイしてクエリを実行すると以下のようになりました。

f:id:keinumata:20200104181712p:plain
映画ランキング一覧クエリ結果

ランキング順に取得できています。(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
      };

デプロイして動作確認してみます。クエリ引数を空にして実行すると以下のようになりました。

f:id:keinumata:20200106224457p:plain
映画一覧クエリ実行結果(引数なし)

映画一覧を公開順に取得できています。取得できた lastKey を基に再実行してみます。

f:id:keinumata:20200106224635p:plain
映画一覧クエリ実行結果(引数あり)

1回目とは異なるデータを取得できました。このままだと映画の公開日が一緒の場合、データが欠損してしまうので今後修正していきたいと思います。

まとめ

  • Apollo Serverを使ってFireStoreのデータを取得した
  • ゾルバーはバックエンドとの接続、データ取得を実装できる