{count, plural, one {Курс} few {Курса} many {Курсов} other {Курса}}
Ручные типы TypeScript для GraphQL API устаревают в тот момент, когда кто-то меняет схему.
Допустим, бэкенд-разработчик переименовал поле title у типа Course в name. Фронтенд всё ещё компилируется, потому что вручную написанный интерфейс Course не знает об изменениях, и баг проявляется только в рантайме. В реальных приложениях десятки запросов, мутаций и фрагментов, так что вы понимаете, как быстро это может выйти из-под контроля.
GraphQL Code Generator (или GraphQL Codegen) читает вашу схему и операции GraphQL и превращает их в типы, хелперы для запросов, интеграции с клиентами и другой шаблонный код, который иначе пришлось бы писать вручную. Чаще всего его используют с TypeScript и популярными клиентами вроде Apollo и urql. Одна команда возвращает ваши типы в синхронизацию при каждом изменении схемы.
В этой статье мы разберём, как настроить GraphQL Codegen в небольшом проекте на TypeScript, сконфигурировать его, использовать сгенерированные типы и запускать его автоматически как часть рабочего процесса.
Чем GraphQL отличается от REST? Прочтите наш полный гид GraphQL vs REST, чтобы узнать о различиях и преимуществах.
Что такое GraphQL Codegen?
GraphQL Codegen — это инструмент командной строки, который превращает определения GraphQL в код приложения.
Это опенсорс-проект от The Guild, который устанавливается как dev-зависимость. Он связывает три сущности:
- Схема GraphQL: контракт между сервером и клиентами со всеми типами, полями и аргументами, доступными в API
- Операции GraphQL: запросы, мутации, подписки и фрагменты, которые ваше приложение отправляет в API
- Сгенерированный код: типы и хелперы, которые Codegen создаёт на основе схемы и ваших операций
Схема говорит Codegen, что возможно, а операции — что именно запрашивает ваше приложение.
Codegen сочетает их, поэтому каждый сгенерированный тип в точности соответствует форме ответа для конкретной операции. Он также проверяет каждую операцию по схеме. Переименованное из введения поле title остановит генерацию с ошибкой вида Cannot query field "title" on type "Course", и вы поймаете проблему до запуска кода.
Результат зависит от плагинов и конфигурации. Сам Codegen не генерирует TypeScript — это делают плагины. Например, плагин typescript генерирует типы по схеме, а typescript-operations — типы для ваших запросов и мутаций. Есть плагины и для других языков, таких как Java и C#, но в этой статье мы остаёмся на TypeScript.
Важно помнить: Codegen не заменяет ваш GraphQL‑сервер.
Он не резолвит запросы и не получает данные. Он читает определения GraphQL во время разработки и записывает файлы. Всё остальное делают ваш сервер и клиент GraphQL.
Как работает GraphQL Codegen
Любая настройка Codegen следует одному и тому же потоку: схема + операции GraphQL -> конфигурация Codegen -> сгенерированный код.

Схема работы GraphQL Codegen
Общая идея такая:
-
Укажите схему: передайте Codegen путь к локальному файлу
.graphqlили к работающей конечной точке GraphQL, которую Codegen прочитает через интроспекцию -
Укажите операции: используйте шаблоны с подстановкой путей, например
src/**/*.ts, чтобы Codegen нашёл запросы, мутации и фрагменты в вашем коде -
Настройте плагины и целевые файлы: укажите, какие файлы писать и какие плагины генерируют каждый из них
-
Запустите Codegen: выполните
npx graphql-codegen, чтобы загрузить схему, провалидировать операции и записать результат -
Используйте артефакты: импортируйте сгенерированные типы и документ‑ноды в код приложения
Шаги 1–3 указываются в одном конфиге, обычно codegen.ts. Вот минимальный вариант для проекта каталога курсов:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
// Step 1: Where the schema comes from
schema: "schema.graphql",
// Step 2: Where your operations are placed
documents: ["src/**/*.ts"],
// Step 3: What to generate and where to write it
generates: {
"./src/gql/": { preset: "client" },
},
};
export default config;
Пресет client — это набор плагинов, который за один проход генерирует типы схемы, типы операций и типизированные документ‑ноды. Каждую из этих частей мы разберём в разделах о настройке и конфигурации.
Как настроить GraphQL Codegen
Лучше всего понять Codegen на реальном проекте.
В этом разделе вы с нуля соберёте небольшой проект каталога курсов. В нём есть схема с курсами и инструкторами, один запрос и конфиг Codegen, который превращает запрос в типы TypeScript. Понадобятся только Node.js и npm.
Шаг 1. Установите GraphQL Code Generator
Создайте новую папку и инициализируйте проект npm:
mkdir course-catalog && cd course-catalog
npm init -y
Codegen нужны несколько пакетов. Два идут в обычные зависимости, остальные — в dev-зависимости, так как используются только при разработке:
npm install graphql @graphql-typed-document-node/core
npm install -D typescript @graphql-codegen/cli @graphql-codegen/typescript-operations @graphql-codegen/typed-document-node
Кратко о назначении каждого пакета:
-
graphql: референсная реализация GraphQL для JavaScript, которую Codegen использует для парсинга схемы и операций -
@graphql-typed-document-node/core: предоставляет типTypedDocumentNode, который импортирует сгенерированный код -
@graphql-codegen/cli: инструмент командной строки Codegen -
@graphql-codegen/typescript-operations: плагин, который генерирует типы для запросов, мутаций и фрагментов -
@graphql-codegen/typed-document-node: плагин, который генерирует объекты документов с прикреплёнными типами
Шаг 2. Создайте файл конфигурации
Codegen читает настройки из конфигурационного файла в корне проекта. Создайте codegen.ts с пустой конфигурацией:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {};
export default config;
Также вы можете запустить npx graphql-codegen init, ответить на несколько вопросов и получить сгенерированный файл конфигурации. Здесь мы пишем его вручную, чтобы было понятно, за что отвечает каждый параметр.
Шаг 3. Укажите расположение схемы и документов
Codegen нужны два входа, поэтому создадим оба.
Сначала сохраните схему как schema.graphql в корне проекта:
enum Level {
BEGINNER
INTERMEDIATE
ADVANCED
}
type Instructor {
id: ID!
name: String!
bio: String
}
type Course {
id: ID!
title: String!
level: Level!
durationHours: Int!
instructor: Instructor!
}
type Enrollment {
id: ID!
course: Course!
studentEmail: String!
}
type Query {
courses(level: Level, limit: Int): [Course!]!
course(id: ID!): Course
}
type Mutation {
enrollStudent(courseId: ID!, studentEmail: String!): Enrollment!
}
Затем создайте src/operations/courses.graphql с запросом, который получает курсы и их инструкторов:
query GetCourses($level: Level, $limit: Int) {
courses(level: $level, limit: $limit) {
id
title
level
instructor {
name
}
}
}
Теперь укажите Codegen оба файла. Ключ schema принимает путь к схеме, а documents — шаблон с подстановкой, подходящий к вашим операциям:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
};
Шаг 4. Выберите файл вывода
Ключ generates говорит Codegen, куда писать результат. Каждый ключ внутри — это путь к файлу:
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/generated/graphql.ts": {},
},
};
Codegen создаст папку src/generated/, если её нет.
Шаг 5. Добавьте нужные плагины
Файл вывода без плагинов будет пустым. Добавьте два установленных плагина:
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "schema.graphql",
documents: ["src/**/*.graphql"],
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
},
},
};
export default config;
Если вы следовали более старому руководству, вероятно, видели в этом списке и плагин typescript. Теперь его можно убрать: typescript-operations работает самостоятельно и генерирует только Input, Enum и типы операций, которые действительно используются. Если оставить оба плагина в одном файле вывода на текущих версиях, TypeScript выдаст ошибки о дублирующихся объявлениях.
Шаг 6. Запустите Codegen
Добавьте npm‑скрипт, чтобы не запоминать команду:
npm pkg set scripts.codegen="graphql-codegen"
Затем запустите его:
npm run codegen
Codegen загружает схему, валидирует по ней запрос и записывает результат:

Результаты генерации Codegen
Если операция ссылается на поле, которого нет в схеме, запуск завершится неуспехом, и файлы записаны не будут.
Шаг 7. Изучите сгенерированный вывод
Откройте src/generated/graphql.ts. Вы увидите следующее:

Сгенерированный вывод
Codegen сгенерировал четыре сущности:
-
Level: строковое объединение для enum, сгенерировано только потому, что оно используется в запросе -
GetCoursesQueryVariables: тип для переменных, передаваемых в запрос, где обе опциональны, так как схема не помечает их как обязательные -
GetCoursesQuery: форма ответа — только те поля, которые запрашивает операция -
GetCoursesDocument: разобранный запрос как типизированная документ‑нода с прикреплёнными типами результата и переменных
Посмотрите на GetCoursesQuery ещё раз. В нём нет durationHours или bio инструктора, потому что запрос их не запрашивает. Если вы попытаетесь обратиться к course.durationHours в коде, получите ошибку компиляции, а не undefined в рантайме.
И на этом вся настройка: вы пишете GraphQL, а Codegen пишет TypeScript.
Как настроить GraphQL Codegen (подробнее)
Большинство конфигов Codegen длиннее приведённого выше, но состоят из тех же четырёх настроек.
Файл конфигурации может называться codegen.ts, codegen.yml или codegen.json. Удобнее TypeScript — он даёт автодополнение и проверку типов самого конфига. Настройки одинаковы в любом формате.
Schema
Ключ schema указывает, где находится ваша схема. Поддерживается несколько источников.
Локальный файл или шаблон с подстановкой — когда схема лежит в репозитории:
schema: "schema.graphql",
// or, for a schema split across files
schema: "schema/**/*.graphql",
URL — когда схема доступна на работающем сервере. Codegen отправляет интроспекционный запрос на эндпоинт, можно передать заголовки для аутентификации:
schema: [
{
"https://api.example.com/graphql": {
headers: { Authorization: Bearer ${process.env.API_TOKEN} },
},
},
],
Вариант с URL работает только если на сервере включена интроспекция. Во многих продакшн‑API она выключена, поэтому для CI обычно надёжнее хранить файл схемы в репозитории.
Documents
Ключ documents указывает, где искать ваши запросы, мутации, подписки и фрагменты.
Операции можно хранить в файлах .graphql или инлайн в TypeScript‑коде. Codegen находит инлайн‑операции, если они обёрнуты в тег gql или помечены комментарием /* GraphQL */:
const EnrollStudent = /* GraphQL */
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
;
Чтобы просканировать оба типа файлов, расширьте шаблон. Добавьте отрицательный паттерн, чтобы Codegen не сканировал свою же генерацию:
documents: ["src/**/*.{graphql,ts}", "!src/generated/**"],
Давайте каждой операции уникальное имя. Codegen строит имена типов из имён операций, поэтому EnrollStudent превращается в EnrollStudentMutation и EnrollStudentMutationVariables.
Generates
Ключ generates сопоставляет пути вывода с тем, что в них попадёт. Вы можете указать столько целей вывода, сколько нужно, у каждой — свои плагины.
Допустим, вы хотите типы операций в одном файле, а полные типы схемы — в другом:
generates: {
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
useTypeImports: true,
},
},
"src/generated/schema-types.ts": {
plugins: ["typescript"],
},
},
Объект config внутри цели применяется только к этому файлу. Здесь useTypeImports заставляет использовать import type, что требуется некоторым сборкам. Если поместить config на корень файла конфига, он применится ко всем выводам.
Путь вывода может быть и папкой, оканчивающейся на слэш, например "src/gql/". Такой формат используют пресеты, так как они пишут больше одного файла.
Plugins
Плагины решают, что именно будет сгенерировано. Codegen загружает схему и документы, а каждый плагин превращает их в свой кусочек вывода.
Чаще всего в TypeScript‑проектах встречаются такие плагины:
-
typescript-operations: типы для запросов, мутаций, подписок и фрагментов, плюс enum и input, которые они используют -
typed-document-node: объекты документов с прикреплёнными типами результата и переменных, понятные большинству клиентов GraphQL -
typescript: типы для каждого объекта, enum и input в схеме — независимо от того, используют ли их ваши операции -
typescript-resolvers: сигнатуры типов для функций резолверов на стороне сервера
Пресет — это набор плагинов с разумной конфигурацией по умолчанию. Пресет client из раздела Как это работает — рекомендуемый The Guild для клиентских приложений. Он базируется на улучшенном typescript-operations, так что вы получаете его преимущества без лишней настройки.
У каждого плагина есть свои опции, и их много. Не нужно учить их заранее. Начните с значений по умолчанию и ищите нужную опцию, когда вывод не совпадает с ожиданиями кода.
Что может генерировать GraphQL Codegen?
Результат Codegen зависит от выбранных плагинов.
Одна и та же схема и операции могут дать как «чистые» типы TypeScript, так и готовый клиентский код. Вот самые частые выводы.
Типы TypeScript
Плагин typescript генерирует тип для каждого объекта, enum и input в вашей схеме. Для каталога курсов тип Course выглядит так:
export type Course = {
__typename?: 'Course';
durationHours: Scalars['Int']['output'];
id: Scalars['ID']['output'];
instructor: Instructor;
level: Level;
title: Scalars['String']['output'];
};
Эти типы описывают полную схему, что удобно на стороне сервера, в тестовых фикстурах и утилитах, работающих с полными объектами.
Но для клиентского кода они подходят хуже. Тип Course говорит, что присутствуют все поля, даже те, которые ваш запрос не запрашивал.
Типы операций
Плагин typescript-operations генерирует по два типа на операцию: один для результата и один для переменных. Аналогично работает для запросов и подписок.
Вот что он генерирует для мутации EnrollStudent:
export type EnrollStudentMutationVariables = Exact<{
courseId: string | number;
studentEmail: string;
}>;
export type EnrollStudentMutation = { enrollStudent: { id: string, course: { title: string } } };
Тип результата совпадает с набором выбираемых полей, поле за полем. Тип переменных — Exact, поэтому TypeScript отвергнет лишние или ошибочно названные переменные.
Переменная courseId принимает и string, и number, так как скаляр GraphQL ID допускает оба варианта на входе. Начиная с v6, тип по умолчанию — string | number, что корректно для клиентских сценариев.
Типизированные документ‑ноды
Обычная DocumentNode — это разобранный запрос GraphQL. Она знает структуру запроса, но не знает, что он возвращает и какие принимает переменные.
Типизированная документ‑нода содержит оба типа. Плагин typed-document-node генерирует по одной на операцию:
export const GetCoursesDocument = { /* ... */ } as unknown as DocumentNode<GetCoursesQuery, GetCoursesQueryVariables>;
Так любая функция, принимающая TypedDocumentNode, может вывести типы результата и переменных только по документу. Не нужно передавать дженерики вручную, и нельзя случайно совместить запрос с неверными типами.
Большинство современных клиентов GraphQL понимают типизированные документ‑ноды, включая Apollo Client, urql и graphql-request. Позже в статье вы увидите их в работе.
Интеграции под конкретные клиенты
Некоторые плагины идут дальше и генерируют код для конкретного клиента или фреймворка. Например:
-
typescript-react-apollo: генерирует типизированные React‑хуки для Apollo Client -
typescript-urql: генерирует типизированные хуки и компоненты для urql -
typescript-react-query: генерирует типизированные хуки для TanStack Query -
typescript-graphql-request: генерирует типизированный SDK с одной функцией на операцию
Все эти плагины — комьюнити. The Guild вынес их в отдельный репозиторий сообщества, поэтому цикл релизов отличается от ядра.
Они также нужны реже, чем раньше. Когда клиент понимает типизированные документ‑ноды, сгенерированные хуки мало что добавляют с точки зрения типобезопасности. Документация Apollo, например, рекомендует использовать плагин typescript-operations, который фокусируется на генерации типов и не добавляет лишнего рантайм‑кода.
В общем случае начните с типов операций и типизированных документ‑нод. Затем добавляйте клиентский плагин только если команде нужны сгенерированные хуки или функции SDK.
GraphQL Codegen с TypeScript
Сгенерированные типы полезны, только если они ловят ошибки — давайте их спровоцируем.
Этот пример расширяет проект каталога курсов вторым запросом, небольшим хелпером для отправки его в API и кодом, который использует результат. Предполагается сервер GraphQL со схемой на http://localhost:4000/graphql.
Вот фрагмент схемы, который используется в примере:
type Instructor {
id: ID!
name: String!
bio: String
}
type Course {
id: ID!
title: String!
level: Level!
durationHours: Int!
instructor: Instructor!
}
type Query {
course(id: ID!): Course
}
Здесь важны две детали. Поле course возвращает Course без !, то есть null, если курс с таким ID не найден. Поле bio у инструктора тоже допускает null.
Создайте src/operations/course.graphql с запросом, который получает один курс:
query GetCourse($id: ID!) {
course(id: $id) {
title
durationHours
instructor {
name
bio
}
}
}
Запустите npm run codegen снова. Codegen добавит два новых типа в src/generated/graphql.ts:

Содержимое файла graphql.ts
Нуллабельность переносится из схемы. course может быть null, bio может быть null, а id обязателен, поскольку в запросе он объявлен как ID!.
Теперь код приложения. Создайте src/execute.ts с хелпером, который отправляет любую типизированную документ‑ноду в API:
import { print } from "graphql";
import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
const API_URL = "http://localhost:4000/graphql";
export async function execute<TResult, TVariables>(
document: TypedDocumentNode<TResult, TVariables>,
variables: TVariables,
): Promise<TResult> {
const response = await fetch(API_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: print(document), variables }),
});
const { data, errors } = await response.json();
if (errors?.length) {
throw new Error(errors[0].message);
}
return data as TResult;
}
Хелпер ничего не знает о курсах. Он получает оба типа из переданной документ‑ноды, а функция print() превращает документ обратно в строку запроса для запроса HTTP.
Затем используйте его в src/course-details.ts:
import { execute } from "./execute";
import { GetCourseDocument, type GetCourseQuery } from "./generated/graphql";
function formatCourse(course: NonNullable<GetCourseQuery["course"]>): string {
const bio = course.instructor.bio ?? "No bio yet";
return course.title({course.durationHours}h) by ${course.instructor.name} - ${bio};
}
const { course } = await execute(GetCourseDocument, { id: "c1" });
if (course) {
console.log(formatCourse(course));
} else {
console.log("Course not found");
}
Функция formatCourse() получает тип параметра из сгенерированного типа запроса. NonNullable убирает вариант null, поскольку функция вызывается только после проверки if.
В примере используется верхнеуровневый await, поэтому добавьте "type": "module" в package.json и запустите npx tsx src/course-details.ts:

Успешный вызов к мок‑серверу GraphQL
Далее предполагается, что у вас запущен сервер GraphQL. Вот мок‑сервер, который использовался:
import { createServer } from "node:http";
import { readFileSync } from "node:fs";
import { createSchema, createYoga } from "graphql-yoga";
const instructors = [{ id: "i1", name: "Dario Radecic", bio: "Developer" }];
const courses = [
{ id: "c1", title: "Intro to GraphQL", level: "BEGINNER", durationHours: 4, instructor: instructors[0] },
{ id: "c2", title: "GraphQL Schema Design", level: "ADVANCED", durationHours: 6, instructor: instructors[0] },
];
const schema = createSchema({
typeDefs: readFileSync("schema.graphql", "utf8"),
resolvers: {
Query: {
courses: (_: unknown, args: { level?: string; limit?: number }) =>
courses
.filter((course) => !args.level || course.level === args.level)
.slice(0, args.limit ?? undefined),
course: (_: unknown, args: { id: string }) =>
courses.find((course) => course.id === args.id) ?? null,
},
Mutation: {
enrollStudent: (_: unknown, args: { courseId: string; studentEmail: string }) => ({
id: "e1",
course: courses.find((course) => course.id === args.courseId),
studentEmail: args.studentEmail,
}),
},
},
});
createServer(createYoga({ schema })).listen(4000, () => {
console.log("Mock API running at http://localhost:4000/graphql");
});
Теперь попробуйте всё сломать. Откройте src/course-details.ts и замените всё ниже функции formatCourse() тремя ошибками, которые вручную написанные типы часто пропускают:
// Missing the required id variable
await execute(GetCourseDocument, {});
const { course } = await execute(GetCourseDocument, { id: "c1" });
// Reading a field the query doesn't request
console.log(course?.level);
// Forgetting that course can be null
console.log(course.title);
Затем запустите проверку типов:
npx tsc -p .
TypeScript поймает все три до запуска кода:

Ошибки, пойманные TypeScript
Так Codegen уменьшает расхождения между фронтендом и API. Типы берутся из той же схемы, что использует сервер, поэтому они не могут описывать несуществующие поля и не пропустят возможные null в ответе сервера.
И защита не заканчивается на первом запуске. Допустим, команда бэкенда сделала durationHours допускающим null. Вы регенерируете, тип меняется на number | null, и TypeScript указывает на каждую строку, где предполагался number.
GraphQL Codegen с клиентами GraphQL
Codegen не отправляет запросы. Этим занимается ваш клиент GraphQL.
Сгенерированные типизированные документ‑ноды — точка передачи между ними. Вы передаёте их клиенту, и клиент выводит типы. Примеры ниже используют тот же файл src/generated/graphql.ts и мок‑API из предыдущего раздела, так что держите npx tsx mock/server.ts запущенным в отдельном терминале.
GraphQL Codegen с Apollo
Установите Apollo Client и его peer‑зависимость rxjs:
npm install @apollo/client rxjs
Пример также выполняет мутацию EnrollStudent. Сохраните её как src/operations/enroll.graphql:
mutation EnrollStudent($courseId: ID!, $studentEmail: String!) {
enrollStudent(courseId: $courseId, studentEmail: $studentEmail) {
id
course {
title
}
}
}
Документация Apollo также рекомендует две опции конфигурации для Codegen, поскольку Apollo Client всегда включает поля __typename, но не добавляет их к корневым типам. Добавьте их в цель вывода в codegen.ts:
"src/generated/graphql.ts": {
plugins: ["typescript-operations", "typed-document-node"],
config: {
nonOptionalTypename: true,
skipTypeNameForRoot: true,
},
},
С этими опциями каждый объект в типах результата получает обязательное поле __typename, например __typename: 'Course'. Это соответствует тому, что возвращает кеш Apollo.
Снова запустите npm run codegen, чтобы учесть новую мутацию и конфиг.
Apollo Client принимает типизированные документ‑ноды в query() и mutate(). Сохраните этот код как src/apollo.ts:
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { EnrollStudentDocument, GetCoursesDocument } from "./generated/graphql";
const client = new ApolloClient({
link: new HttpLink({ uri: "http://localhost:4000/graphql" }),
cache: new InMemoryCache(),
});
const { data } = await client.query({
query: GetCoursesDocument,
variables: { level: "BEGINNER", limit: 5 },
});
data?.courses.forEach((course) => console.log(course.title));
const result = await client.mutate({
mutation: EnrollStudentDocument,
variables: { courseId: "c1", studentEmail: "student@example.com" },
});
console.log(result.data?.enrollStudent.course.title);
Объект data имеет тип GetCoursesQuery | undefined, а TypeScript проверяет variables на соответствие GetCoursesQueryVariables. Для мутации всё работает аналогично с EnrollStudentMutation и её переменными.
Запустите npx tsx src/apollo.ts:

Вывод Apollo
Первая строка приходит из запроса — это единственный курс для начинающих. Вторая — из мутации, которая возвращает название курса, на который записался студент.
React‑хуки, такие как useQuery() и useMutation(), выводят типы из типизированных документ‑нод аналогично.
GraphQL Codegen с urql
Установите базовый пакет urql:
npm install @urql/core
urql «из коробки» работает с типизированными документ‑нодами. Передайте документ первым аргументом в query() в src/urql.ts:
import { Client, cacheExchange, fetchExchange } from "@urql/core";
import { GetCoursesDocument } from "./generated/graphql";
const client = new Client({
url: "http://localhost:4000/graphql",
exchanges: [cacheExchange, fetchExchange],
});
const result = await client
.query(GetCoursesDocument, { level: "ADVANCED" })
.toPromise();
if (result.error) {
throw result.error;
}
result.data?.courses.forEach((course) => console.log(course.title));
Второй аргумент типизируется как переменные запроса, а result.data — как результат запроса.
Запустите npx tsx src/urql.ts:

Вывод urql
Фреймворк‑биндинги, например useQuery() в пакете urql для React, тоже принимают типизированные документ‑ноды.
GraphQL Codegen с другими клиентами
Для получения типизированных результатов не всегда нужен полнофункциональный клиент.
Клиент graphql-request — это минималистичный клиент, который отправляет один запрос и возвращает данные. Установите его командой:
npm install graphql-request
Он выводит типы из типизированных документ‑нод так же, как Apollo и urql. Сохраните этот код как src/graphql-request.ts:
import { request } from "graphql-request";
import { GetCourseDocument } from "./generated/graphql";
const { course } = await request(
"http://localhost:4000/graphql",
GetCourseDocument,
{ id: "c2" },
);
console.log(course?.title);
Запустите npx tsx src/graphql-request.ts:

Вывод graphql-request
Та же идея работает с TanStack Query. Он сам не отправляет запросы GraphQL, поэтому вызывайте graphql-request или хелпер execute() из предыдущего раздела внутри функции запроса, и тип результата пройдёт до вашего кода.
Если ваш клиент совсем не поддерживает типизированные документ‑ноды, всё равно можно использовать «чистые» типы операций. Импортируйте GetCourseQuery и GetCourseQueryVariables и передайте их как дженерики. Это чуть более вручную, но типы всё так же приходят из схемы.
Как запускать GraphQL Codegen автоматически
Сгенерированные типы полезны только тогда, когда они соответствуют текущей схеме и операциям.
Если вы забыли перезапустить Codegen после изменения, типы будут описывать код, которого уже нет. Это та же проблема дрейфа, что и у ручных типов, только со лишним шагом. Решение — убрать ручной шаг из процесса.
npm‑скрипты
Начните с пары скриптов в package.json, чтобы вся команда запускала Codegen одинаково:
{
"scripts": {
"codegen": "graphql-codegen",
"codegen:watch": "graphql-codegen --watch",
"codegen:check": "graphql-codegen --check",
"prebuild": "npm run codegen",
"build": "tsc -p ."
}
}
Далее — о назначении каждого скрипта.
Режим наблюдения
Watch‑режим перезапускает Codegen при каждом изменении наблюдаемых файлов. Ему нужен пакет @parcel/watcher, который Codegen сам не устанавливает:
npm install -D @parcel/watcher
npm run codegen:watch
Если пропустить установку, Codegen выполнится один раз и выведет Failed to import @parcel/watcher вместо наблюдения.
Режим наблюдения отслеживает и операции, и локальный файл схемы. Если добавить поле в course.graphql, новое поле появится в GetCourseQuery через секунду. Если сделать durationHours допускающим null в schema.graphql, тип изменится на number | null, и редактор подсветит все строки, где предполагалось число.
Держите его запущенным в терминале рядом с dev‑сервером во время разработки.
Генерируйте перед сборкой
npm запускает скрипт prebuild перед build. С указанными выше скриптами npm run build сначала регенерирует типы, а уже затем запускает компилятор TypeScript.
Так сборка никогда не пойдёт со старыми типами, даже если кто-то забыл запустить Codegen после git pull.
CI/CD
Флаг --check запускает Codegen в режиме «сухого прогона». Он ничего не записывает, сравнивает то, что бы сгенерировал, с тем, что на диске, и выходит с кодом 1 при любом расхождении.
Вот минимальный workflow GitHub Actions, который его использует:
name: CI
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npm run codegen:check
- run: npm run build
Шаг codegen:check имеет смысл только если вы коммитите сгенерированные файлы в репозиторий. Если нет — опустите его, ведь npm run build всё равно генерирует их заново через хук prebuild.
Регенерируйте при изменении схемы
Операции меняются в вашем репозитории, так что их покрывают watch‑режим и prebuild. С изменениями схемы сложнее — они обычно приходят из другой команды.
Подход зависит от того, где хранится схема:
- Файл схемы в том же репозитории: watch‑режим и CI подхватывают изменения — самый простой вариант
- Схема по URL: Codegen на каждом запуске подтягивает последнюю версию, так что
codegen:checkв CI упадёт, как только схема сервера перестанет совпадать с зафиксированным выводом - Схема из другого репозитория: скопируйте или скачайте её как часть сборки, затем запустите Codegen на локальной копии
У варианта с URL есть нюанс. Watch‑режим реагирует на изменения файлов, поэтому не заметит, если удалённая схема поменяется. Вы увидите такие изменения только при следующем ручном запуске или в CI.
GraphQL Codegen против ручных типов
Оба подхода дают вам типы TypeScript для данных GraphQL, но есть важные различия.
Ручные типы
При ручных типах вы читаете схему и сами пишете соответствующие интерфейсы.
Так вы получаете полный контроль. Можно называть типы как угодно, объединять несколько операций в один общий тип или добавлять вспомогательные типы, которых нет в схеме.
Но это ручной труд. Каждый новый поле или переименование аргумента значит, что нужно найти и обновить соответствующий тип вручную. И ничего не предупредит, если вы что-то пропустите.
Отсюда и берётся дрейф. Интерфейс Course из введения всё ещё компилировался после переименования title на бэкенде, потому что TypeScript проверяет ваш код по вашим типам, а не по API.
Сгенерированные типы
Сгенерированные типы приходят из той же схемы, что использует сервер, так что по дизайну они синхронизированы с вашими определениями GraphQL. Изменение в схеме плюс запуск Codegen обновляют все затронутые типы сразу.
Вы также пишете гораздо меньше кода. В проекте каталога курсов — три операции и ноль ручных типов для них.
И вы почти бесплатно получаете разработку, управляемую схемой. Меняете схему или операцию, регенерируете и получаете от компилятора список всего, что нужно поправить.
Но важно помнить: Codegen отражает только то, что вы ему даёте.
Если в схеме все поля допускают null, ваши сгенерированные типы будут полны проверок | null. Если операция запрашивает 40 полей, когда странице нужно пять, в сгенерированном типе будет 40 полей.
Коротко о различиях:
| Ручные типы | Сгенерированные типы | |
|---|---|---|
| Поддержка | Вручную при каждом изменении схемы | Одна команда после каждого изменения |
| Дрейф схемы | Возможен и незаметен | Ловится на этапе генерации или компиляции |
| Шаблонный код | Растёт с каждой операцией | Не пишется вручную |
| Контроль над кастомными типами | Полный | Ограничен опциями плагинов |
| Разработка, управляемая схемой | Ручная сверка | Компилятор показывает все затронутые строки |
Сравнение Codegen и ручных типов
GraphQL Codegen vs. интроспекция GraphQL
Их часто путают, потому что Codegen нередко использует интроспекцию. Но задачи у них разные.
Интроспекция — это часть спецификации GraphQL. Любой сервер GraphQL может отвечать на запросы о своей схеме через специальные поля, такие как __schema и __type. Результат — JSON‑описание каждого типа, поля и аргумента, которые предоставляет API.
Проще говоря, интроспекция отвечает на вопрос «Как выглядит этот API?»
Codegen отвечает на вопрос «Какой код нужен моему приложению?». Он берёт информацию о схеме и ваши операции и пишет типы TypeScript и типизированные документ‑ноды.
Когда вы задаёте schema как URL в codegen.ts, Codegen отправляет интроспекционный запрос на этот сервер, чтобы получить схему. С локальным файлом .graphql интроспекции не происходит вовсе.
Codegen также может выпускать результат интроспекции как вывод. Плагин @graphql-codegen/introspection записывает схему в формате JSON интроспекции, который используют некоторые инструменты, например Graphcache в urql, для кеширования с учётом схемы.
| Интроспекция | GraphQL Codegen | |
|---|---|---|
| Что это | Встроенная система запросов GraphQL | Инструмент CLI и плагины |
| Вход | Запрос к работающему серверу | Схема и ваши операции |
| Выход | JSON‑описание схемы | Типы, документ‑ноды, клиентский код |
| Когда выполняется | Всякий раз, когда клиент запрашивает у сервера | Во время разработки, сборок и в CI |
Сравнение Codegen и интроспекции
Частые проблемы с GraphQL Codegen
Большинство ошибок Codegen сводятся к одной из четырёх причин.
Сгенерированные типы отсутствуют
Codegen проходит без ошибок, но ожидаемого типа нет в файле вывода.
Проверьте три вещи по порядку:
-
Схема: если типа нет в схеме, которую загружает Codegen, он не может быть сгенерирован — откройте файл схемы или выполните интроспекцию сами и убедитесь, что тип существует
-
Документы: типы операций существуют только для операций, которые нашёл Codegen, поэтому если файл с вашим запросом не подходит под шаблон в
documents, его типы будут отсутствовать -
Плагины: начиная с v6,
typescript-operationsгенерирует только те enum и input, которые используют ваши операции; если вам нужен полный тип схемы вродеCourse, добавьте плагинtypescriptв отдельный файл вывода
Если вы используете плагин, который не установлен, Codegen упадёт с понятным сообщением:
Unable to find template plugin matching 'typescript-react-apollo'
Install one of the following packages:
- @graphql-codegen/typescript-react-apollo
Codegen не находит операции
Эта ошибка означает, что шаблон documents не совпал ни с одним файлом, содержащим операции:
Unable to find any GraphQL type definitions for the following pointers:
- src/**/*.gql
Проверьте, что расширение в шаблоне совпадает с вашими файлами. Паттерн .gql не найдёт файлы .graphql, а src/**/*.graphql не найдёт инлайн‑операции в файлах .ts.
Для инлайн‑операций проверьте и тег. Codegen находит только шаблонные строки, обёрнутые в gql, или помеченные комментарием /* GraphQL */. Обычная template‑строка с запросом для него невидима.
Если указать ignoreNoDocuments: true, Codegen не покажет эту ошибку и сгенерирует вывод без типов операций. Опция полезна в новом проекте, но впоследствии может скрыть поломанный шаблон.
Сгенерированные типы устарели
Типы компилируются, но не отражают последние изменения в операции или схеме.
Codegen сам по себе не запускается. Перезапускайте его после каждого изменения файла .graphql, инлайн‑операции, схемы или codegen.ts. Во время разработки это делает npm run codegen:watch.
Если типы устарели в CI, закоммитьте регенерированные файлы или позвольте хуку prebuild сгенерировать их перед сборкой.
Сгенерированные типы не совпадают с API
Типы компилируются, но данные от сервера имеют другую форму в рантайме.
Почти всегда это означает, что Codegen и приложение работают с разными схемами. Например, Codegen читает локальный schema.graphql недельной давности, а приложение обращается к продакшн‑API, который с тех пор изменился.
Проверьте, куда указывает ключ schema. Если это файл — сравните его с API, к которому обращается приложение, и обновите. Если это URL — убедитесь, что это то же окружение, что использует приложение, а не staging с более новой или старой схемой.
Затем регенерируйте и посмотрите diff. Если изменился файл операции, а сгенерированный — нет, операция, скорее всего, вне шаблона documents.
Лучшие практики для GraphQL Codegen
Codegen не требует сложной конфигурации, чтобы хорошо работать. Эти практики помогут сохранить простоту по мере роста проекта:
-
Держите схему и сгенерированные файлы синхронными: запускайте watch‑режим во время разработки и
prebuildперед сборками, чтобы никто не работал со старыми типами -
Генерируйте типы под конкретные операции: используйте
typescript-operationsдля клиентского кода — они в точности соответствуют тому, что запрашивает каждая операция -
Коммитьте сгенерированные файлы только при необходимости процесса: зафиксированные файлы позволяют ревьюить изменения типов в pull‑request и запускать
--checkв CI; если это не нужно, добавьте папку вывода в.gitignoreи генерируйте на каждой сборке -
Держите конфиг коротким: одного файла вывода с двумя плагинами хватает для большинства клиентских проектов, а каждая лишняя опция — это то, что следующему разработчику нужно понять перед изменениями
-
Запускайте Codegen в CI при каждом пуше: шаг
--checkили хукprebuildпоймает изменения, которые кто-то забыл сгенерировать локально -
Никогда не редактируйте сгенерированные файлы вручную: следующий запуск Codegen перезапишет изменения — правьте схему, операцию или конфигурацию
Заключение
GraphQL Codegen берёт на себя самую рутинную часть разработки с GraphQL — превращение схем и операций в типы TypeScript вручную.
Рабочий процесс остаётся одинаковым, независимо от масштаба проекта. Вы определяете схему и операции, настраиваете Codegen, генерируете типы и типизированные документ‑ноды и импортируете их в код приложения.
Главная ценность — ваши типы всегда описывают именно тот API, к которому вы обращаетесь, а не код, который больше не нужно писать. Когда схема меняется, компилятор подскажет, что сломалось, чтобы вы могли поймать ошибки заранее.
Если хотите узнать, как GraphQL используется в современной NoSQL‑базе данных, прочтите наш пост MongoDB и GraphQL.
Частые вопросы о GraphQL Codegen
Для чего используется GraphQL Codegen?
GraphQL Codegen генерирует код из схемы GraphQL и ваших операций GraphQL. Большинство команд используют его, чтобы получать типы TypeScript и типизированные документ‑ноды для своих запросов и мутаций, чтобы не писать и не обновлять эти типы вручную. Он также может генерировать типы резолверов для серверов и готовые хуки для конкретных клиентов.
Нужен ли запущенный сервер GraphQL для использования Codegen?
Нет. Codegen может читать схему из локального файла .graphql, так что он работает без какого‑либо сервера. Запущенный сервер нужен только если вы укажете URL в ключе schema, потому что тогда Codegen получает схему через интроспекцию.
Стоит ли коммитить сгенерированные файлы в Git?
Зависит от рабочего процесса вашей команды. Зафиксированные в Git файлы позволяют просматривать изменения типов в pull‑request и использовать graphql-codegen --check в CI, чтобы обнаруживать устаревший вывод. Если это не требуется, добавьте папку вывода в .gitignore и регенерируйте файлы перед каждой сборкой.
Использовать пресет client или отдельные плагины?
The Guild рекомендует пресет client для большинства клиентских приложений, так как он объединяет типы операций, типизированные документ‑ноды и маскирование фрагментов в одной настройке. Apollo рекомендует вместо него плагин typescript-operations, потому что пресет добавляет рантайм‑код и функции, конфликтующие с Apollo Client. Если вы используете Apollo — берите плагины. В противном случае пресет — хороший дефолт.
Почему после обновления Codegen я получаю ошибки о дублирующихся объявлениях?
Начиная с v6, плагин typescript-operations сам генерирует типы enum и input. Если в вашем конфиге по‑прежнему указаны оба — typescript и typescript-operations — для одного и того же файла вывода, оба плагина объявят типы вроде Level, и TypeScript выдаст ошибки о дублирующихся объявлениях. Уберите плагин typescript из этого вывода или перенесите его в отдельный файл, если всё ещё нужны полные типы схемы.