🙌

GraphQLを素振りしてみた

2022/05/14に公開

概要

2014年にFacebookがGraphQLを発表してからもう8年が経ちました。自分は今までRESTful APIをRailsやExpressで構築したことしかなかったので色々あって去年の年末から素振りしていたGrahpQLについて書いてみることにしました。
構築したアプリケーションはこちらです。素振り目的なので未完成な部分がありますがお許し下さい。ちなみにCSSはTailwind CSS(V2)を使っています。いい加減V3にアップデートしないと笑。

https://next-rails-playground.vercel.app/

自分が読んだ記事

以前からGraphQLに関する記事は読んでいたので全くわからなった訳ではありませんでした。

https://qiita.com/saboyutaka/items/171f7382cdf75b67d076
https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad
https://eh-career.com/engineerhub/entry/2018/12/26/103000

構成

バックエンド

GraphQL Rubyを使ってRailsで構築したGraphQLサーバをHerokuにデプロイしています。ユーザのアイコン画像を設定できるようにしたかったのでストレージだけはAWSのS3を使っています。

フロントエンド

URLからわかる通りNext.jsをVercelにデプロイしています笑。
データ通信のコードは

https://www.graphql-code-generator.com/

を使って自動生成しています。これは感動しましたね・・・。.graphqlファイルを定義してコマンドを叩くだけで型安全なhooksのコードが自動で生成されてそれをコンポーネントから呼び出すだけで良いわけですから工数を大幅に削減することができました。

技術的な事

バックエンド

GraphQLに関しては全く触ったことがなかったのでとりあえず以下のリンクのtutorialをなぞりました。

https://www.howtographql.com/graphql-ruby/0-introduction/

  • 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で実行すると良さそうですね。

https://qiita.com/saiidalhalawi@github/items/5853904d0bbdcdddfed3

フロントエンド

Apollo Clientの使い方やGraphQL Code Generatorについて知る必要があったので以下の記事を読みました。

https://zenn.dev/eringiv3/books/a85174531fd56a
https://zenn.dev/ytr0903/articles/84adf3c09dfb99
https://uncle-javascript.com/graphql-codegen-japan-user-group

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にはあるのになんでコードが生成されないんだ〜?

とかつまづいていました。

あと余裕があったら

https://www.graphql-code-generator.com/docs/advanced/generated-files-colocation

こちらを使って生成される./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の方は詳しくはドキュメントを読んで下さいという感じなのですが、onCompletedonErrorでmutationが完了した後やエラーが発生した時の処理を書くことができます。

感想

  • GraphQLすごい
  • 型があるって素敵だな
  • フロントのコード自動生成できるの神
  • GraphQLはバックエンドの技術だと思っていたがフロントエンドとバックエンドが協調して扱う技術だと認識
  • TypeScriptでApollo Server書いたらさらに開発体験上がりそう
  • RailsでGraphQL Ruby使うとロジックがapp/graphql配下に全て収まるので初見だとちょっと戸惑いそう
  • 今回HerokuとVercelの無料プランを使ったので検証にかかった費用は0円でした。良い時代に生まれたものです。

Discussion