18. GraphQL Codegen

GraphQL Code Generator автоматически генерирует TypeScript типы из твоей схемы. Больше никакого ручного написания интерфейсов!
Зачем нужен Codegen
Заголовок раздела «Зачем нужен Codegen»Без Codegen — ручная типизация, которая расходится со схемой:
// ❌ Вручную — легко пропустить измененияinterface User { id: string; name: string; email: string;}
// Эти типы могут не совпадать с реальной схемой GraphQL!С Codegen — всё генерируется из схемы:
// ✅ Автоматически из схемыexport type User = { __typename?: 'User'; id: Scalars['ID']['output']; name: Scalars['String']['output']; email: Scalars['String']['output']; posts: Array<Post>;};Установка
Заголовок раздела «Установка»npm install -D @graphql-codegen/clinpx graphql-code-generator initИли вручную:
npm install -D \ @graphql-codegen/cli \ @graphql-codegen/typescript \ @graphql-codegen/typescript-operations \ @graphql-codegen/typescript-react-apolloКонфигурация
Заголовок раздела «Конфигурация»schema: "http://localhost:4000/graphql"
# Или из файла схемы:# schema: "./schema.graphql"
documents: - "src/**/*.{ts,tsx}" - "src/**/*.graphql"
generates: # Типы схемы (для клиента и сервера) src/generated/graphql.ts: plugins: - typescript - typescript-operations - typescript-react-apollo config: withHooks: true withComponent: false withHOC: false
# Resolver типы (только для сервера) server/generated/resolvers.ts: schema: "./server/schema.graphql" plugins: - typescript - typescript-resolvers config: contextType: "../src/types#Context" mappers: User: "../models#UserModel" Post: "../models#PostModel"Или в JSON:
{ "schema": "http://localhost:4000/graphql", "documents": ["src/**/*.{ts,tsx,graphql}"], "generates": { "src/generated/graphql.ts": { "plugins": [ "typescript", "typescript-operations", "typescript-react-apollo" ], "config": { "withHooks": true } } }}{ "scripts": { "codegen": "graphql-codegen --config codegen.yml", "codegen:watch": "graphql-codegen --config codegen.yml --watch" }}npm run codegen# Генерирует src/generated/graphql.tsЧто генерируется
Заголовок раздела «Что генерируется»Типы схемы
Заголовок раздела «Типы схемы»// src/generated/graphql.ts (автоматически)
export type Scalars = { ID: { input: string; output: string; }; String: { input: string; output: string; }; Int: { input: number; output: number; }; Float: { input: number; output: number; }; Boolean: { input: boolean; output: boolean; }; DateTime: { input: string; output: string; };};
export type User = { __typename?: 'User'; id: Scalars['ID']['output']; name: Scalars['String']['output']; email: Scalars['String']['output']; role: UserRole; posts: Array<Post>; createdAt: Scalars['DateTime']['output'];};
export enum UserRole { Admin = 'ADMIN', Editor = 'EDITOR', Reader = 'READER'}
export type CreatePostInput = { title: Scalars['String']['input']; body: Scalars['String']['input']; tags?: InputMaybe<Array<Scalars['String']['input']>>;};Хуки для операций
Заголовок раздела «Хуки для операций»После запуска codegen с typescript-react-apollo, вместо:
// ❌ Без codegen — вручнуюconst GET_USER = gql` query GetUser($id: ID!) { user(id: $id) { id name email } }`;
// Нет типизации! data.user имеет тип anyconst { data } = useQuery(GET_USER, { variables: { id } });Используешь:
// ✅ С codegen — полная типизацияimport { useGetUserQuery, GetUserDocument,} from '../generated/graphql';
// Полная типизация! data.user — типизированный объектconst { data, loading, error } = useGetUserQuery({ variables: { id: userId },});
// Автодополнение работает!console.log(data?.user.name); // stringconsole.log(data?.user.email); // stringПример использования
Заголовок раздела «Пример использования»# src/queries/user.graphql (или прямо в tsx)query GetUserProfile($id: ID!) { user(id: $id) { id name email avatar bio posts(limit: 5) { id title createdAt } }}
mutation UpdateProfile($name: String, $bio: String) { updateProfile(name: $name, bio: $bio) { id name bio }}После npm run codegen:
// Автоматически сгенерировано:export function useGetUserProfileQuery( baseOptions: Apollo.QueryHookOptions<GetUserProfileQuery, GetUserProfileQueryVariables>) { return Apollo.useQuery<GetUserProfileQuery, GetUserProfileQueryVariables>( GetUserProfileDocument, baseOptions );}
export function useUpdateProfileMutation( baseOptions?: Apollo.MutationHookOptions<UpdateProfileMutation, UpdateProfileMutationVariables>) { return Apollo.useMutation<UpdateProfileMutation, UpdateProfileMutationVariables>( UpdateProfileDocument, baseOptions );}Используешь в компоненте:
import { useGetUserProfileQuery, useUpdateProfileMutation,} from '../generated/graphql';
function UserProfilePage({ userId }: { userId: string }) { // Полная типизация! const { data, loading } = useGetUserProfileQuery({ variables: { id: userId }, });
const [updateProfile, { loading: saving }] = useUpdateProfileMutation();
if (loading) return <Spinner />;
return ( <div> <h1>{data?.user.name}</h1> <p>{data?.user.bio}</p> {/* TypeScript знает, что posts — массив объектов с id, title, createdAt */} <ul> {data?.user.posts.map(post => ( <li key={post.id}> {post.title} — {new Date(post.createdAt).toLocaleDateString()} </li> ))} </ul> </div> );}Resolver типы (сервер)
Заголовок раздела «Resolver типы (сервер)»npm install -D @graphql-codegen/typescript-resolvers// server/generated/resolvers.ts (автоматически)export type Resolvers = { Query: QueryResolvers; Mutation: MutationResolvers; User: UserResolvers; Post: PostResolvers;};
export type QueryResolvers = { user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<QueryUserArgs, 'id'>>; posts?: Resolver<Array<ResolversTypes['Post']>, ParentType, ContextType, Partial<QueryPostsArgs>>; me?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType>;};// resolvers/user.ts — типизированные resolversimport { Resolvers } from '../generated/resolvers';
export const userResolvers: Resolvers = { Query: { user: (_, { id }, { db }) => db.users.findById(id), // Типизировано! me: (_, __, { user }) => user, }, User: { posts: (parent, { limit }, { db }) => // parent типизирован как User db.posts.findByAuthor(parent.id, { limit }), },};Кастомные скаляры в codegen
Заголовок раздела «Кастомные скаляры в codegen»generates: src/generated/graphql.ts: plugins: - typescript config: scalars: DateTime: string URL: string JSON: Record<string, unknown> Upload: FileNear-Operation Types (продвинутый паттерн)
Заголовок раздела «Near-Operation Types (продвинутый паттерн)»generates: src/: preset: near-operation-file presetConfig: extension: .generated.ts baseTypesPath: generated/graphql.ts plugins: - typescript-operations - typescript-react-apolloКаждый файл получает свои типы рядом:
src/├── components/│ ├── PostCard.tsx│ └── PostCard.generated.ts # Автоматически!└── pages/ ├── BlogPage.tsx └── BlogPage.generated.ts # Автоматически!Практика
Заголовок раздела «Практика»- Установи graphql-codegen в проект с Apollo Client
- Настрой
codegen.ymlдля генерации типов и хуков - Перепиши 2-3 компонента с
useQueryна сгенерированные хуки - Добавь resolver типы на сервере
- Настрой watch режим для автогенерации при изменении файлов
- Codegen генерирует TypeScript типы из GraphQL схемы
- Больше нет ручных интерфейсов, которые расходятся со схемой
- Генерируются хуки:
useGetUserQuery,useCreatePostMutation, etc. - Resolver типы — типизация на сервере
--watchрежим — автообновление при изменениях
Следующий урок → GraphQL vs REST →