GraphQL Code Generator メモ

入門編
バックエンドからフロントエンドまで、GraphQL Code Generatorはその生成を自動化します。
GraphQL 操作の型付けを自動化および生成することは、開発者の体験とスタックの安定性の両方を向上させます。
フロントエンドでの使い方
GraphQLの操作タイプを手動で管理したり、タイプが完全にない場合は、多くの問題が発生する可能性があります。
- outdated typing (regarding the current Schema)
- typos
- partial typing of data (not all Schema's fields has a corresponding type)
type Author {
id: Int!
firstName: String!
lastName: String!
posts(findTitle: String): [Post]
}
type Post {
id: Int!
title: String!
author: Author
}
type Query {
posts: [Post]
}
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'https://localhost:4000/graphql',
documents: ['src/**/*.tsx'],
generates: {
'./src/gql/': {
preset: 'client',
}
}
}
export default config
React Queryの場合
import { request } from 'graphql-request'
import { useQuery } from '@tanstack/react-query'
import { graphql } from './gql/gql'
// postsQueryDocument is now fully typed!
const postsQueryDocument = graphql(/* GraphQL */ `
query Posts {
posts {
id
title
author {
id
firstName
lastName
}
}
}
`)
const Posts = () => {
// React Query `useQuery()` knows how to work with typed graphql documents
const { data } = useQuery<PostQuery>('posts', async () => {
const { posts } = await request(endpoint, postsQueryDocument)
return posts
})
// `data` is fully typed!
// …
}
- 常に最新な型
- すべてのクエリ、ミューテーション、サブスクリプション変数、結果に対するオートコンプリート
- Reactのフック生成のような完全なコード生成のおかげで、ボイラープレートも少なくて済む。
バックエンドでの使い方
ほとんどのGraphQL APIリゾルバは型付けされていない、または間違った型付けをしたままであり、これが複数の問題につながります。
- リゾルバがスキーマの定義に準拠していない
- リゾルバーの関数型シグネチャのタイプミス
このため、GraphQL Code Generatorでは、リゾルバのタイピングの生成を自動化するためのプラグインを複数用意しています。
以下は、GraphQL Code Generatorのリゾルバタイピングを活用したGraphQL APIの例です(上記のschema.graphqlをベースにしています)。
Apollo Serverの場合
import { readFileSync } from 'node:fs'
import { ApolloServer } from 'apollo-server'
import { Resolvers } from './resolvers-types'
const typeDefs = readFileSync('./schema.graphql', 'utf8')
const resolvers: Resolvers = {
Query: {
// typed resolvers!
}
}
const server = new ApolloServer({ typeDefs, resolvers })
// The `listen` method launches a web server
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
})
Guides
- React
- Appolo Server
React
以下のQueryを使ってシンプルなGraphQLフロントエンドアプリを構築し、スターウォーズの映画リストを取得します。
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
and its FilmItem Fragment definition:
fragment FilmItem on Film {
id
title
releaseDate
producers
}
import { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'https://swapi-graphql.netlify.app/.netlify/functions/index',
documents: ['src/**/*.tsx'],
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
'./src/gql/': {
preset: 'client'
}
}
}
export default config
Usage with react-query
Writing GraphQL Queries
React Queryの場合
import React from 'react'
import request from 'graphql-request'
import { useQuery } from '@tanstack/react-query'
import './App.css'
import Film from './Film'
import { graphql } from '../src/gql'
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
query allFilmsWithVariablesQuery($first: Int!) {
allFilms(first: $first) {
edges {
node {
...FilmItem
}
}
}
}
`)
function App() {
// `data` is typed!
const { data } = useQuery(['films'], async () =>
request('https://swapi-graphql.netlify.app/.netlify/functions/index', allFilmsWithVariablesQueryDocument, {
first: 10 // variables are typed too!
})
)
return (
<div className="App">
{data && <ul>{data.allFilms?.edges?.map((e, i) => e?.node && <Film film={e?.node} key={`film-${i}`} />)}</ul>}
</div>
)
}
export default App
提供されているgraphql()関数(../src/gql/)を使用して、GraphQLクエリまたはMutationを定義するだけで、お気に入りのクライアントにGraphQLドキュメントを渡すだけですぐに型付き変数と結果を得ることができます!
Writing GraphQL Fragments
GraphQL フラグメントは、より優れた分離と再利用可能な UI コンポーネントの構築に役立ちます。
ReactでのFilmUIコンポーネントの実装を見てみましょう。
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
export const FilmFragment = graphql(/* GraphQL */ `
fragment FilmItem on Film {
id
title
releaseDate
producers
}
`)
const Film = (props: {
/* `film` property has the correct type 🎉 */
film: FragmentType<typeof FilmFragment>
}) => {
const film = useFragment(FilmFragment, props.film)
return (
<div>
<h3>{film.title}</h3>
<p>{film.releaseDate}</p>
</div>
)
}
export default Film
<FilmItem>コンポーネントは、生成されたコード(../src/gql)から、FragmentType<T>型ヘルパーとuseFragment()関数という2つのインポートを利用していることがわかるでしょう。
- FragmentType<typeof FilmFragment>を使って、対応するFragmentのTypeScript型を取得します。
- その後、useFragment()を使ってfilmプロパティを取得する。
FragmentType<typeof FilmFragment>と useFragment()を活用することで、UIコンポーネントを分離し、親GraphQL Queryの型付けを継承しないようにします。
おめでとうございます!これで、完全に型付けされたクエリーとミューテーションで、最高のGraphQLフロントエンド体験を手に入れました。
Appolo Server
Config Reference
codegen.ts
GraphQL Code Generatorは、codegen.tsという設定ファイルに依存して、すべての可能なオプション、入力、および出力ドキュメント タイプを管理します。
schema field
- schemaフィールドはGraphQLSchemaを指す必要があり、これを指定してGraphQLSchemaを読み込む方法は複数あります。
- schemaには、スキーマを指す文字列か、マージされる複数のスキーマを指す文字列[]を指定することができます。
documents field
- documentsフィールドは、GraphQLドキュメント(query、mutation、subscription、fragment)を指す必要があります。
- documentsは、クライアントサイド用のコードを生成するプラグインを使用する場合のみ必要です。
- 指定できるのは、自分の文書を指す文字列か、複数の文書を指す文字列[]のどちらかです。
plugin config
- configフィールドは、プラグインに設定を渡すために使用されます。
- configは、多くのレベルで指定することができます。
- ルートレベル
- 出力レベル
- プラグインレベル
require field
requireフィールドは、事前にトランスパイルする必要なく、任意の外部ファイルを読み込むことができます。
plugin
クライアント
- typescript
- typescript-operations
- typescript-graphql-request or typescript-react-query
サーバーサイド
- @graphql-codegen/typescript
- @graphql-codegen/typescript-resolvers
参考にするといいかも記事