[GraphQL] TypeScript+VSCode+Apolloで最高のDXを手に入れよう

GraphQLを利用するWebアプリケーションの開発で、TypeScript + VSCode + Apollo のスタックで開発環境を構成すると、とても最高なDX(Developer Experience)が得られたので、紹介したいと思います。

どのくらい最高かを想像しやすくするため、従来のBADなDXの例を挙げてみますと...「APIのドキュメントに誤りがあり通信に失敗する」「APIの変更がドキュメントに反映されていなくてサーバサイドのエンジニアに問い合わせる」「APIのドキュメントを見ながらAPIリクエスト/レスポンスのオブジェクトを手作りで実装する」などなど、よくある話かと思います。従来のREST APIなどでは実装とドキュメントが分離されているので、そこを埋めるためのエンジニアの作業が必要です(そこをツールで埋めようとする試みは古くからありますが)。一方で、GraphQLは実装とスキーマ定義が一体なので、無理なくツールの力を借りやすくなっており、エンジニアの作業を省く(=DXを向上させる)ことができます。

前置きが長くなりましたが、このスタックで得られるDXは次のとおりです。

  • VSCode上でQueryやMutationの入力補完を使うことができる
  • QueryやMutationのレスポンスの型を自動生成できる

環境

  • TypeScript: 3.7.2
  • VSCode: 1.40.1
  • Apollo GraphQL(VSCode拡張): 1.12.1
  • apollo-tooling(CLI): 2.21.0

サンプルの説明

この記事では、実装のサンプルとしてswapi-graphqlからデータを取得して表示するプロジェクトを作成しましたのでそれを例に上げながら説明します。お手元の環境にgit cloneして、ぜひ実際に動作を体験してみてください。

https://github.com/bisque33/graphql-client-dx

QueryやMutationの入力補完

手順

  • VSCodeに拡張機能の Apollo GraphQL を追加します。
  • apollo.config.jsにGraphQLサーバの接続情報を設定します。
  • VSCodeのコマンドパレットで Apollo: Reload schema を実行します。

以上の設定を終えると、 gql タグで囲った内部で、入力補完が働くようになります。

レスポンスの型を自動生成

手順

  • apollo-toolingをインストールします。インストールせずにnpxを使っても良いです。
  • client:codegen コマンドを実行します。 apollo client:codegen --target typescript types

以上の手順を終えると、 gql タグで囲った内部に定義したQueryやMutationのレスポンスの型が自動生成されます。またEnumやInput Objectの型定義も自動生成されます。

./src/types/GetFilms.ts

/* tslint:disable */
/* eslint-disable */
// This file was automatically generated and should not be edited.

// ====================================================
// GraphQL query operation: GetFilms
// ====================================================

export interface GetFilms_allFilms_films {
  __typename: "Film";
  /**
   * The episode number of this film.
   */
  episodeID: number | null;
  /**
   * The title of this film.
   */
  title: string | null;
}

export interface GetFilms_allFilms {
  __typename: "FilmsConnection";
  /**
   * A list of all of the objects returned in the connection. This is a convenience
   * field provided for quickly exploring the API; rather than querying for
   * "{ edges { node } }" when no edge data is needed, this field can be be used
   * instead. Note that when clients like Relay need to fetch the "cursor" field on
   * the edge to enable efficient pagination, this shortcut cannot be used, and the
   * full "{ edges { node } }" version should be used instead.
   */
  films: (GetFilms_allFilms_films | null)[] | null;
}

export interface GetFilms {
  allFilms: GetFilms_allFilms | null;
}

これを useQuery の型引数として渡してやればOKです。

const GET_FILMS = gql`
  query GetFilms {
    allFilms {
      films {
        title
        episodeID
      }
    }
  }
`

const Contents: React.FC = () => {
  const { loading, error, data } = useQuery<GetFilms>(GET_FILMS)

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error :</div>
  if (!data) return <div>Error : data is undefined</div>

  return (
    <ul>
      {data.allFilms!.films!.map(film => (
        <li>
          episode {film!.episodeID}: {film!.title}
        </li>
      ))}
    </ul>
  )
}

おわりに

Apolloのエコシステムを利用することで、GraphQLを利用するクライアントアプリケーションの実装が省力化できることがおわかりいただけたかと思います。今回紹介したのと同じような内容がGraphQL Summit 2019のセッションにもありますので、ぜひこちらも御覧になってください。

The GraphQL developer experience