見出し画像

GraphQL Code Generator で型定義の安全性の高める

エンジニアの fujino です。
急に涼しくなりましたね。みなさま健康状態はいかがでしょうか?
美味しいものを食べて適度に運動する秋にしたいですね🍁

実は先日、巷で噂のリングフィットアドベンチャーの抽選に当選しました。届くだけで3kg痩せる気がしています。とても楽しみです。

さて今回は、GraphQL + TypeScript開発を快適にしてくれるライブラリ、GraphQL Code Generator を紹介したいと思います。

GraphQL + TypeScript における型定義

GraphQL と TypeScript は性質上どちらも型定義が必要になります。フロントエンドの実装では、TypeScriptの型をGraphQLのスキーマ定義を見ながら手作業で実装する、という非常に手間となる作業が発生します。時間もかかるうえにバグの原因となる可能性もあります。

そこで今回、型安全なコードを書くために導入されたのが、GraphQL Code Generator というライブラリです。

本記事ではGraphQL (apollo client) + TypeScript におけるスキーマ定義をコマンドひとつで生成する方法を紹介します。

ディレクトリ構造は以下のようになります(必要な箇所だけ記載)
また、本記事は apollo-client を使用した実装例になっております。

├── graphql
│   ├── fragment
│   ├── generated  // このディレクトリ配下に型定義を生成
│   │   ├── introspectionResultData.ts
│   │   └── schema.tsx
│   ├── mutations
│   └── queries
~~省略~~
└── codegen.js  // 設定ファイル

生成するファイル

上記のディレクトリ構造の通り、今回は2つのファイルを作成します。

introspectionResultData.ts
apollo-clientを使用してスキーマにインターフェースが使われているため、Fragment Matcher というプラグインで、イントロスペクションファイル(利用できるデータ型のリスト)を作成します。

schema.tsx
GraphQLで定義したスキーマからTypeScriptで型定義が生成されたスキーマファイルです。生成には TypeScriptTypeScript OperationsTypeScript React Apollo というプラグインを使います。

graphql-codegenの導入

基本的なGraphQLパッケージがあることを前提として、以下を実行します。(パッケージ導入していなければ yarn run add graphql を実行)

// graphql-codegen の導入
yarn add -D @graphql-codegen/cli @graphql-codegen/typescript

// 設定ファイルの作成
yarn graphql-codegen init

// プラグインの追加
// schema用
yarn add -D @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo

// Fragment Matcher用
yarn add -D @graphql-codegen/fragment-matcher

// 実行
yarn generate​

yarn graphql-codegen init により、codegen.yml が生成されますが、弊社では jsファイル で以下のような設定にしました。

// e.g.
// $ GRAPHQL_API_HOST='https://hoge-api.hoge.com' yarn run graphql-codegen

const host =
 process.env.GRAPHQL_API_HOST || "https://hoge-api.hoge.com";
console.log(`Fetch schema from ${host}`)

module.exports = {
 schema: `${host}/graphql`,
 overwrite: true,
 documents: ['./src/**/*.graphql'],
 generates: {
   "./src/graphql/generated/introspectionQueryResultData.ts": {
     plugins: ["fragment-matcher"],
   },
   "./src/graphql/generated/schema.tsx": {
     plugins: [
       "typescript",
       "typescript-operations",
       "typescript-react-apollo",
     ],
     config: {
       withComponent: false,
       withHOC: false,
       withHooks: true,
     }
   },
 },
};

上記の設定により、src/**/*.graphql ファイルをドキュメントとして型定義ファイルが生成されるようになりました。

生成された型定義コードを利用する

では実際に .graphql ファイルを元にどのような型定義が生成され、クライアント側でどのように利用するのかを具体的に紹介します。

src/HomeAreaList/index.graphql (定義する)

query HomeAreaList {
 regions {
   results {
     id
     name
     nameText
     largeAreas {
       results {
         id
         name
         nameText
       }
     }
   }
 }
}

schema.tsx (yarn generateで生成)

export type HomeAreaListQueryVariables = {};

export type HomeAreaListQuery = (
 { __typename?: 'Query' }
 & { regions?: Maybe<(
   { __typename?: 'RegionList' }
   & { results?: Maybe<Array<Maybe<(
     { __typename?: 'Region' }
     & Pick<Region, 'id' | 'name' | 'nameText'>
     & { largeAreas?: Maybe<(
       { __typename?: 'LargeAreaList' }
       & { results?: Maybe<Array<Maybe<(
         { __typename?: 'LargeArea' }
         & Pick<LargeArea, 'id' | 'name' | 'nameText'>
       )>>> }
     )> }
   )>>> }
 )> }
);

export const HomeAreaListDocument = gql`
   query HomeAreaList {
 regions {
   results {
     id
     name
     nameText
     largeAreas {
       results {
         id
         name
         nameText
       }
     }
   }
 }
}
   `;

/**
* __useHomeAreaListQuery__
*
* To run a query within a React component, call `useHomeAreaListQuery` and pass it any options that fit your needs.
* When your component renders, `useHomeAreaListQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useHomeAreaListQuery({
*   variables: {
*   },
* });
*/
export function useHomeAreaListQuery(baseOptions?: ApolloReactHooks.QueryHookOptions<HomeAreaListQuery, HomeAreaListQueryVariables>) {
       return ApolloReactHooks.useQuery<HomeAreaListQuery, HomeAreaListQueryVariables>(HomeAreaListDocument, baseOptions);
     }
export function useHomeAreaListLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<HomeAreaListQuery, HomeAreaListQueryVariables>) {
         return ApolloReactHooks.useLazyQuery<HomeAreaListQuery, HomeAreaListQueryVariables>(HomeAreaListDocument, baseOptions);
       }
export type HomeAreaListQueryHookResult = ReturnType<typeof useHomeAreaListQuery>;
export type HomeAreaListLazyQueryHookResult = ReturnType<typeof useHomeAreaListLazyQuery>;
export type HomeAreaListQueryResult = ApolloReactCommon.QueryResult<HomeAreaListQuery, HomeAreaListQueryVariables>;

ここで生成されたコードのhooks(!)を利用します。useHomeAreaListQuery によってクエリを叩いてデータを取得できます。

import React, { useState } from 'react'
import { useHomeAreaListQuery } from 'src/graphql/generated/schema'
~~中略~~

const { data } = useHomeAreaListQuery()

~~中略~~

// dataの中身がスキーマ定義通りに型付けされている
  const regions = data.regions.results.map((region) => ({
  
~~省略~~

VSCodeにてファイルを確認すると、data.regionsで型の定義が表示されていることがわかります。

スクリーンショット 2020-09-22 21.57.06

このように、GraphQL Code Generator を導入することによって、GraphQLのスキーマ定義を利用して安全性を高めた型定義(TypeScript)を簡単に取り入れることができました🎉

まとめ

導入前は .graphql ファイルからクエリをexportし、クライアント側で取得した data に対する型を再定義しなければならない状態でした。GraphQL Code Generator を導入することで、自らの手で再定義する手間が省けたうえに安全性も高まりました。開発体験が大きく変わって最高だったので、紹介させていただきました。

皆さんもぜひ、graphql-codegen を使って快適なフロントエンド開発を感じてください😊

いただいたサポートは自己研鑽用に使わせていただきますmm