GraphQLを素振りしてみた
概要
2014年にFacebookがGraphQLを発表してからもう8年が経ちました。自分は今までRESTful APIをRailsやExpressで構築したことしかなかったので色々あって去年の年末から素振りしていたGrahpQLについて書いてみることにしました。
構築したアプリケーションはこちらです。素振り目的なので未完成な部分がありますがお許し下さい。ちなみにCSSはTailwind CSS(V2)を使っています。いい加減V3にアップデートしないと笑。
自分が読んだ記事
以前からGraphQLに関する記事は読んでいたので全くわからなった訳ではありませんでした。
構成
バックエンド
GraphQL Rubyを使ってRailsで構築したGraphQLサーバをHerokuにデプロイしています。ユーザのアイコン画像を設定できるようにしたかったのでストレージだけはAWSのS3を使っています。
フロントエンド
URLからわかる通りNext.jsをVercelにデプロイしています笑。
データ通信のコードは
を使って自動生成しています。これは感動しましたね・・・。.graphql
ファイルを定義してコマンドを叩くだけで型安全なhooksのコードが自動で生成されてそれをコンポーネントから呼び出すだけで良いわけですから工数を大幅に削減することができました。
技術的な事
バックエンド
GraphQLに関しては全く触ったことがなかったのでとりあえず以下のリンクのtutorialをなぞりました。
- query, mutationの実装の仕方
- 認証
- エラーハンドリング
- ページネーション
- 検索
など基本的な項目をソースコード付きで解説してくれているので大変分かりやすかったです。tutorialを1周した後自分でモデルを追加してqueryやmutationを作ったりしてなんとなく理解しました。
触っている中で1resolver1ファイルで管理した方が良いかなと感じました。queryは app/graphql/types/query_type.rb
に、mutationはapp/graphql/types/mutation_type.rb
に直接定義できるのですが、複雑なアプリケーションだと各resolverの記述が多くなると思うので、結果としてapp/graphql/types/query_type.rb
及びapp/graphql/types/mutation_type.rb
が肥大化することが懸念されます。チーム開発だとコンフリクトが多発する原因にもなるので何か事情がない限り1resolver1ファイルを方針として取り決めても良いのかなと思いました。
また、resolverを追加したらschema.graphql
を更新する必要があり、調べたところこちらの記事のスキーマファイルの生成
のセクションが参考になりました。CIで実行すると良さそうですね。
フロントエンド
Apollo Clientの使い方やGraphQL Code Generatorについて知る必要があったので以下の記事を読みました。
codegen.ymlはこんな感じで定義しました。
overwrite: true
schema: http://localhost:3005/graphql
documents:
- ./src/graphql/queries/*.graphql
- ./src/graphql/mutations/*.graphql
generates:
./src/types/generated/types.d.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
config:
useIndexSignature: true
enumsAsTypes: true
withHooks: true
withRefetchfn: true
gplImport: 'graphql-tag'
./graphql.schema.json:
plugins:
- 'introspection'
間違っている部分とかありましたらコメントください。
documents:
- ./src/graphql/queries/*.graphql
- ./src/graphql/mutations/*.graphql
こんな感じに定義すると対象のディレクトリにある.grahpql
ファイルを読み取ってhooksを自動生成してくれます。最初はこれを知らずにコマンドを叩いて
あれ〜、graphql.schemaにはあるのになんでコードが生成されないんだ〜?
とかつまづいていました。
あと余裕があったら
こちらを使って生成される./src/types/generated/types.d.ts
を分割したいというお気持ちがあります。ざっと見た感じそこまで導入のコストは高くなさそうですね。
あとpageでquery, mutationを呼ぶコードを載せておきます。
const { loading, data } = useCategoryQuestionsQuery({
variables: {
categoryId: router.query.id as string,
page: 1,
},
})
if (loading || !data) return <div>ローディングなう...</div>
return (
<div>
{data.categoryQuestions.questions.map((item) => (
<QuestionCard
key={item.id}
id={item.id.toString()}
title={item.title}
content={item.content}
/>
))}
</div
)
const router = useRouter()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [signInUser, { loading }] = useSignInUserMutation({
variables: {
email,
password,
},
onCompleted: (result) => {
localStorage.setItem('profile', JSON.stringify(result.signInUser?.user))
router.push('/')
},
onError(error) {
alert(error.message)
},
})
const submitLoginForm = async (e: FormEvent) => {
e.preventDefault()
await signInUser()
}
queryの方は特に解説は必要ないかと思います。dataには取得したデータが、loadingにはqueryが実行中か否かを判定するbooleanが入っているのでそれに従ってレンダリングしてください。
mutationの方は詳しくはドキュメントを読んで下さいという感じなのですが、onCompleted
やonError
でmutationが完了した後やエラーが発生した時の処理を書くことができます。
感想
- GraphQLすごい
- 型があるって素敵だな
- フロントのコード自動生成できるの神
- GraphQLはバックエンドの技術だと思っていたがフロントエンドとバックエンドが協調して扱う技術だと認識
- TypeScriptでApollo Server書いたらさらに開発体験上がりそう
- RailsでGraphQL Ruby使うとロジックが
app/graphql
配下に全て収まるので初見だとちょっと戸惑いそう - 今回HerokuとVercelの無料プランを使ったので検証にかかった費用は0円でした。良い時代に生まれたものです。
Discussion