Next.js + Vercel + TypeScript + Firebaseでアプリを作る。GraphQL事前準備編
0. 今回やること
前回の記事はこちら。
今回は、GraphQLを使うための前準備をする。
やることとしては、Cloud Firestoreに入っているデータをGraphQLを使って返す、と言う感じ。
1. 事前準備
データの設定
まずはFirestoreの設定から。
と言っても、今回はまだデプロイしたりはしないので、どちらかと言うと開発用のデータの設定からやっていく。
今回使うデータは以下:
userProfiles: [ { id: 1, bio: "hogehoge" }, { id: 2, bio: "fugafuga" } ]
このデータを、Dockerコンテナの中で firebase emulators:start
をして立ち上がるローカル用コンソール画面 http://localhost:4000
の Firestore
の項目から入力。
その後、一度コンテナ内で
$ firebase emulators:export ./seed
をして、プロジェクトルートディレクトリ直下の /seed
というディレクトリにこのデータをエクスポートしておく。
その後、package.json
のスクリプトにあるserve
を
"serve": "npm run build && firebase emulators:start --only functions"
から
"serve": "firebase emulators:start --import=../seed",
にしておく。
こうすることで、エミュレータ立ち上げ時に、エクスポートしたデータがインポートされる(npm run build
を無くした理由は後述)。
ただ、これだと手動でエクスポートする羽目になるので、前回の記事にも書いたこの方法をしたほうがいいかも。
<2020/11/26 追記>
Firebase Firestoreのエミュレーターを使ってローカルで開発する←こちらを読ませてもらったところ、--export-on-exit
というすごく便利なoptionがあることがわかった。
/hogehoge # firebase emulators:start -h Usage: firebase emulators:start [options] start the local Firebase emulators Options: ...中略... --import [dir] import emulator data from a previous export (see emulators:export) --export-on-exit [dir] automatically export emulator data (emulators:export) when the emulators make a clean exit (SIGINT), when no dir is provided the location of --import [dir] is used -h, --help output usage information
とのことらしいので、このオプションもつけておけばよさそう。
感謝。
公式を見てもエミュレータの細かい使い方がいまいちわからなかったんだけど、-h
やるのは大事だね。
Cloud Functionsで使うTypeScriptファイルの自動再コンパイル設定
やることとしては、
tsc --watch
を追加するだけなのだが、package.json
のスクリプトを
"serve": "tsc --watch && firebase emulators:start --import=../seed"
という風にしても、tsc --watch
と firebaseエミュレータの両方でターミナルを使うため、うまく行かない。
なので 、
"watch": "tsc --watch", "serve": "firebase emulators:start --import=../seed",
という風にして、watch
とエミュレータを異なるターミナルで実行できるようにした。
ターミナルのタブを2つ使うのがめんどくさいけど、もっと良い方法があるのかな。
これで事前準備は終わり。
2. Apollo Serverの設定
次はApollo Serverの設定をしていく。
Apollo ServerはGraphQLを用いてのデータのやり取りをするためのサーバー。
Cloud Functions向けのライブラリも出てるので、これを使うことにした。
と言っても、今の段階では
Cloud Functions for Firebaseを使ってApollo Serverを構築する - 雑食日誌
Apollo ServerからFireStoreのデータを取得する - 雑食日誌
こちらの記事を参考にさせてもらった感じ。
まずは必要なライブラリのインストールから。
# npm i apollo-server-cloud-functions
で、現時点でのfunctions/src/index.ts
は
const { ApolloServer, gql } = require('apollo-server-cloud-functions'); const typeDefs = gql` type UserProfile { id: Int! bio: String! } type Query { userProfiles: [UserProfile]! } `; const resolvers = { Query: { userProfiles: async () => { const snapshot = await db.collection('userProfiles').get() return snapshot.docs.map((doc) => doc.data()) }, }, } const server = new ApolloServer({ typeDefs, resolvers, }) export const helloWorld = functions.https.onRequest(server.createHandler())
という感じ。
resolvers
は別ファイルに分けてもいいかも。
これでコンパイルが通ったら、http://localhost:5001/<エミュレータ立ち上げ時に出るpath>/helloWorld
にPostmanなどを使ってPOST
で
query { userProfiles { id, bio } }
のようなリクエストを投げて、設定したデータが返ってきたらOK。
この記事を書いてて気づいたけど、resolvers
は別ファイルにしたほうがよさそう。
3. GraphQLの設定
↑の例だと、gql
に渡すテンプレートリテラルの中でGraphQLのスキーマを設定しているので他のファイルに外出ししたい。
ちなみに、Apolloのサンプルコードも大体スキーマはテンプレートリテラルに直書きとなってることが多い。
ただ、これだとスキーマが増えたりした時に可読性が下がって萎えるので、今のうちからファイルを分けておくことにした。
色々ググってみたところ、GraphQL Tools
のこの記事が役に立ちそうだったので、これでやってみる。
まずは必要なライブラリのインストールから。
# npm i graphql-tools
で、ディレクトリ構成はこんな感じにした:
. └── /project root/ └── /functions/ ├── /src/ │ └── index.ts // Cloud Functions用のファイル ├── /graphql/ │ ├── /schemas/ │ │ └── userProfile.gql │ └── /queries/ │ └── userProfiles.gql └── /lib // index.tsのコンパイル後のファイルの格納先
各gqlファイルは以下:
# functions/graphql/schemas/userProfile.gql type UserProfile { id: Int! bio: String! }
# functions/graphql/queries/userProfiles.gql type Query { userProfiles: [UserProfile]! }
その後、gqlファイルを読み込むために functions/src/index.ts
を以下のように変更:
# functions/src/index.ts import * as functions from 'firebase-functions' import * as admin from 'firebase-admin' import { ApolloServer } from 'apollo-server-cloud-functions' import path from 'path' import { mergeTypeDefs, loadFilesSync } from 'graphql-tools' const typesArray = loadFilesSync(path.resolve(__dirname, '../graphql'), { recursive: true, }) const typeDefs = mergeTypeDefs(typesArray) admin.initializeApp() const db = admin.firestore() const resolvers = { Query: { userProfiles: async () => { const snapshot = await db.collection('userProfiles').get() return snapshot.docs.map((doc) => doc.data()) }, }, } const server = new ApolloServer({ typeDefs, resolvers, }) export const helloWorld = functions.https.onRequest(server.createHandler())
これでテンプレートリテラルにスキーマを書かなくても良くなった。
4. VSCodeの設定
最後に、VSCodeでスキーマの定義をしやすくするために、ファイルをまたいでの補完ができるようにしておく。
GraphQLのスキーマ定義に役立つVSCodeの拡張機能をざっと見た限り、
https://marketplace.visualstudio.com/items?itemName=GraphQL.vscode-graphql:title
https://marketplace.visualstudio.com/items?itemName=kumar-harsh.graphql-for-vscode:title
の2つが主だった拡張機能の様子。
ただ、設定方法を見ると、graphql-for-vscode
はvscode-graphql
に比べて、watchmanや@playlyfe/gqlと言ったライブラリの事前インストールが必要というところが面倒に感じたので、vscode-graphql
を使うことにした。
なので、まずVSCodeでこの拡張機能をインストール後、ルートディレクトリ直下に.graphqlrc.yml
という設定ファイルを作成し、設定手順にあるとおりに以下の内容を記述する:
# .graphqlrc.yml schema: ./functions/graphql/**/*.gql documents: null
ちなみに、この設定ファイルは、JSON形式やjsファイルでも書けるようになっている。
内部でGraphQL Config
を使っているようで、GraphQL Configのドキュメントにより設定ファイルの書き方の詳細が載っている。
その後、functions/graphql/queries/userProfiles.gql
でUserProfile
と入力して補完されればOK...となるはずだが、現時点(v0.3.12)では補完が効かない。
色々調べてみると、こういうissueが上がっており、要はconfigファイルschema
の項目にglob
を使うとダメらしい。
なので、今はこの書き方をしても補完はされない。
リポジトリのREADMEには
Note: The primary maintainer @acao is on hiatus until December 2020
とあるので、解決まではもう少し待つことになりそう。
一応回避策として、
scehma: - functions/graphql/queries/userProfiles.gql - functions/graphql/schemas/userProfile.gql
というように列挙すれば補完が効くようになるが、これはすごーくめんどくさいので待った方がよさそう。
それかgraphql-for-vscode
を使うか。
5. 所感
流石にサンプルコードレベルじゃ勉強の意味が薄いなと思ってファイル分割とか開発の利便性あたりまで踏み込んでみたけど、ここまで手こずるとは思わなかった。
特にVSCodeの拡張の謎の不具合は本当に罠だったー。
でもまあ最終的に原因が分かったからよかった。
また、今回はリクエストが来るたびに全てのスキーマファイルを読み込むようにしてるけど、ここにあるように、本番では事前にファイルに書き出したスキーマをつかうようにしてもいいのかもなあ、と思った。
まあ、この辺は追々やって行こう。
次はいよいよGraphQLの踏み込んだ使い方とFirestoreのデータ設計に入って行こうと思う。
6. その他参考にしたサイト等
GraphQL | A query language for your API
Documentation Home - Apollo Basics - Apollo GraphQL Docs
Welcome | GraphQL Tools
Firestore エミュレータのデータをローカル環境で import/export する - Qiita