GraphQL プチ勉強会を開いたよ

こんにちは。とろろかけ牛丼が食べたい亀山です。

先日弊社 Web サイトを Wordpress から Wordpress をバックエンドとして用いた Next.js への移行を行いました。移行の際に導入した GraphQL と React をつかったワークフローが未体験で面白かったので、社内へ展開するためハンズオン形式でプチ勉強会を行いました。本記事ではその時に使った資料を記事として共有致します。

GraphQL とは

  • REST API のようにサーバとクライアントでデータをやり取りできるもの
  • REST API と違って、リクエストする側でレスポンスに含める情報を指定できる
    • 必要なものだけ取得できるので無駄がない
  • どんな型のどんなデータが取れるかというスキーマを取得できる <- 重要

見てみよう

  • GitHub の API があるよ
  • GraphiQL という GUI ツールがデファクト
    • スキーマをポチポチ見たり、クエリを GUI で作れる

docs.github.com

React から取得してみよう

  • JavaScript のサンプルプロジェクト

github.com

ハンズオン

  • クエリを書き換えてスター数 (stargazerCount) を表示してみよう
    • GraphiQL でクエリを作ろう
    • コンポーネントを書き換えよう

型をつけよう

  • TypeScript のサンプルプロジェクト

github.com

ハンズオン TypeScript

  • クエリを書き換えてスター数 (stargazerCount) を表示してみよう
    • さっきのクエリを入れてみる
    • 保存するとコード生成が走り、型が付きます
    • コンポーネントを書き換えよう

面白いところ

  • TypeScript のコードと GraphQL のスキーマを統合した型定義が生成される
    • 基本的にはコード生成でエラーが出なければ動くクエリになっている
  • コード生成するのに参照した TypeScript のソースファイルの中で生成された型定義を使うところ
    • スキーマから静的な型定義が生成されて、それを利用するだけという一方方向の使い方ではなく、コードからも型定義を生成するというアクロバティックな構成
    • Swagger とかを理解していると逆にしっくりこないかもしれない
  • クライアントサイドで手書きで string などの具体的な型指定を行わない世界観
    • API から提供されたスキーマがもとになるので、number だっけ?string だっけ?optional だっけ?みたいな迷いや間違いがない

Fragment とは

  • クエリから受け取るレスポンスの形式の指定だけを抽出したもの
  • 色々なクエリで再利用できる

ハンズオン: Fragment を定義しよう

  • さっきのクエリを Fragment に分けてみよう
gql(`
  fragment RepositoryData on Repository {
    id
    name
  }
`)

const GET_REPOSITORIES = gql(`
  query GetRepositories {
    search(query: "swift", type: REPOSITORY, first: 10) {
      nodes {
        ...RepositoryData
      }
    }
  }
`)

ハンズオン: Fragment Colocation してみよう

  • リポジトリ一つを表示するコンポーネントを作ろう
  • コンポーネント側に Fragment を移動しよう
  • Fragment をマージしてみよう

コンポーネント

Fragment の型は <Fragment名>Fragment として生成されます。

export interface RepositoryRowProps {
  repository: RepositoryDataFragment
}

export function RepositoryRow({ repository }: RepositoryRowProps) {
  return (
    <div key={repository.id}>
      <p>{repository.name}</p>
    </div>
  )
}

Fragment Colocation の嬉しいところ

  • View は気軽に変わるのでレスポンスに必要なデータが何かちゃんと管理するのは難しい
  • Fragment Colocation を実施すると、View 側で必要なデータを定義できるので過不足なく指定できる
  • 型定義はスキーマから生成しているので、コンパイルが通れば正しいクエリを書いている保証になる

ハンズオン: RepositoryRow をリッチにしよう

リポジトリの作成日や、作者のユーザーアイコンなどを追加してみよう

このとき、RepositoryList やそのクエリを変更することなく、RepositoryRow 内の変更だけで済む喜びを味わってください。

注意点

  • gql./__generated__/graphql から import してください。@apollo/client から import すると型がつきません
  • fragment などを書き換えた後に VSCode 上で TypeScript のコードにエラーがある状態になることがあります。コード生成が終わったタイミングでもう一度保存すると解消されます

どうやって型つけされているか

  • gql 関数はデフォルトでは string を引数にとるものしか無いが、コード生成によって、特定のクエリの文字列を引数に取る関数がオーバーロードされている
export function gql(source: string): unknown;
export function gql(source: "\n  query GetRepositories {\n    search(query: \"swift\", type: REPOSITORY, first: 10) {\n      nodes {\n        ...RepositoryRow_repository\n      }\n    }\n  }\n"): (typeof documents)["\n  query GetRepositories {\n    search(query: \"swift\", type: REPOSITORY, first: 10) {\n      nodes {\n        ...RepositoryRow_repository\n      }\n    }\n  }\n"];