GraphQL APIのTypescriptの型をClient側で自動生成する
2021/09/05
openapi2aspidaみたいにswagger.yml
からtypescriptの型を自動生成してくれるのが便利すぎて、GraphQLでもgraphql
ファイルから型を自動生成するやつないかな〜と思っていたら普通にありました。
その名もGraphQL Code Genratorです。
今回はGraphQL Code Generatorを使わない場合と使う場合で実際のコードがどう変化するのかを見ながら、その便利さを紹介できたらと思います。
ちなみにフレームワークはReact + Apollo Clientを想定しています。
Apollo Client with Typescript
GraphQL Code Generatorを使用しない場合
Apollo ClientをReactで使用する場合は通常、以下のようなコードになります。
import React from 'react';
import { useQuery, gql } from '@apollo/client';
type User = {
id: number;
age: number;
name: string;
};
type UserData = {
users: User[];
};
type UserVars = {
age: number;
};
const GET_USERS = gql`
query GetUsers($age: !Int) {
users(age: $age) {
id
age
name
}
}
`;
export const UserList: React.FC = () => {
const { data } = useQuery<UserData, UserVars>(GET_USERS, {
variables: { age: 28 },
});
return (
<ul>
{data.users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
};
十分実用的ではありますが、やはりUserData
とかUserVar
とかの型定義が若干めんどくさいですね...
めんどくさがり屋の自分としては
const GET_USERS = gql`
query GetUsers($age: !Int) {
users(age: $age) {
id
age
name
}
}
`;
からある程度の型は推測してほしいと思ってしまいます🥺
GraphQL Code Generatorを使用した場合
では続いてGraphQL Code Generatorを使用した場合どうなるか見てみます。
以下のようなschema.graphql
が用意できてるとして進めます。
type User {
id: Int!
name: String!
age: Int!
}
type Query {
users(age: Int!): [User!]!
}
使用したいqueryを書いたgraphql
ファイルを用意して
query users($age: Int!) {
users(age: $age) {
id
age
name
}
}
以下のGraphQL Code GeneratorのCLIコマンドを実行すると...
$ npx graphql-codegen
次のような型情報が入ったtypescriptファイルが生成されます。
※ 実行時にschema.graphql
とuser.graphql
の間に不整合があった場合はエラーが発生するのでご注意ください(不整合チェックに使えて便利👏)。
generated.ts (Clickで詳細)
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
const defaultOptions = {}
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export type Query = {
__typename?: 'Query';
users: Array<User>;
};
export type QueryUsersArgs = {
age: Scalars['Int'];
};
export type User = {
__typename?: 'User';
id: Scalars['Int'];
name: Scalars['String'];
age: Scalars['Int'];
};
export type UsersQueryVariables = Exact<{
age: Scalars['Int'];
}>;
export type UsersQuery = { __typename?: 'Query', users: Array<{ __typename?: 'User', id: number, age: number, name: string }> };
export const UsersDocument = gql`
query users($age: Int!) {
users(age: $age) {
id
age
name
}
}
`;
/**
* __useUsersQuery__
*
* To run a query within a React component, call `useUsersQuery` and pass it any options that fit your needs.
* When your component renders, `useUsersQuery` 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 } = useUsersQuery({
* variables: {
* age: // value for 'age'
* },
* });
*/
export function useUsersQuery(baseOptions: Apollo.QueryHookOptions<UsersQuery, UsersQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<UsersQuery, UsersQueryVariables>(UsersDocument, options);
}
export function useUsersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<UsersQuery, UsersQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<UsersQuery, UsersQueryVariables>(UsersDocument, options);
}
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
export type UsersQueryResult = Apollo.QueryResult<UsersQuery, UsersQueryVariables>;
※ 実際にnpx graphql-codegen
で上記のような型ファイルを生成するには、事前にcodegen.yml
ファイルを用意しておく必要があります(具体的には後述の導入方法のsectionに記載しています)。
生成されたtypescriptファイルをimportして最初のtsx
ファイルを以下のように書くことができます。
import React from 'react';
import { useUsersQuery } from './graphql/generated';
export const UserList: React.FC = () => {
// dataやvariablesの型もしっかり定義されている
const { data } = useUsersQuery({ variables: { age: 28 } });
return (
<ul>
{data.users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
};
型を自分で定義する必要がなくなったのでだいぶスッキリしました✨
GraphQL Code Generator導入方法
GraphQL Code Generatorの導入方法ですが、Apollo Clientを使用する場合、必要なパッケージは以下の通りです。
- @graphql-codegen/cli
coreパッケージ - @graphql-codegen/typescript
typexcriptの型生成する場合に必用なプラグイン - @graphql-codegen/typescript-operations
GraphQL のクエリとスキーマを元にtypescriptの型を自動生成するプラグイン - @graphql-codegen/typescript-react-apollo
生成されたファイルから直接apollo/clientを使えるようにするプラグイン
$ yarn add -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo
続いて以下のcodegen.yml
ファイルをプロジェクト直下に用意します。
schema: ./path/to/schema.graphql
documents:
# user.graphqlなどのqueryやmutationが書かれたファイルを指定
- ./graphql/**/*.graphql
generates:
graphql/generated.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
これで導入は完了です。
npx graphql-codegen
を実行すると型情報が記載されたts
ファイルが生成されると思います。
おわり
APIの型は自動生成する時代 🏄♂️