TypeScript с GraphQL — типизация и кодогенерация

18 сентября 2026
Автор

Антон Ларичев

Зачем типизировать GraphQL-запросы

GraphQL уже гарантирует корректность данных на уровне схемы — но только на сервере. Клиентский код без типизации по-прежнему работает с any, что делает рефакторинг опасным, а автодополнение — недоступным.

Рассмотрим типичную проблему:

const { data } = useQuery(GET_USER);
// data имеет тип any
console.log(data.user.naem); // опечатка — TypeScript промолчит

Типизация GraphQL решает несколько задач:

  • Ошибки несоответствия типов обнаруживаются на этапе компиляции, а не в продакшене
  • IDE предлагает автодополнение по полям запроса
  • Рефакторинг схемы немедленно подсвечивает затронутые места в клиентском коде
  • Фрагменты получают собственные типы и переиспользуются безопасно

Курс по теме

Пройти курс TypeScript с нуля

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Ручная типизация — отправная точка

До появления инструментов кодогенерации типы писали вручную.

// Определяем типы вручную
interface User {
  id: string;
  name: string;
  email: string;
  role: 'ADMIN' | 'USER' | 'GUEST';
}

interface GetUserQuery {
  user: User | null;
}

interface GetUserQueryVariables {
  id: string;
}

// Используем с Apollo Client
const { data } = useQuery<GetUserQuery, GetUserQueryVariables>(GET_USER, {
  variables: { id: '123' },
});

// Теперь data.user.naem вызовет ошибку TypeScript
console.log(data?.user?.name); // ok

Проблема ручного подхода — синхронизация. При изменении схемы сервера придётся вручную обновлять все интерфейсы. В крупных проектах это становится источником ошибок.

GraphQL Code Generator

graphql-codegen — де-факто стандарт для автоматической генерации TypeScript-типов из GraphQL-схемы и клиентских операций. Он читает схему сервера и файлы с запросами, после чего генерирует точные типы для каждой операции.

Установка

npm install -D @graphql-codegen/cli @graphql-codegen/client-preset

Для проектов с Apollo Client дополнительно потребуется:

npm install -D @graphql-codegen/typescript @graphql-codegen/typescript-operations

Настройка codegen.yml

Создайте файл конфигурации в корне проекта:

# codegen.yml
schema: 'http://localhost:4000/graphql'
documents: 'src/**/*.graphql'
generates:
  src/gql/:
    preset: client
    presetConfig:
      gqlTagName: gql
    config:
      scalars:
        DateTime: string
        UUID: string

Опция schema указывает на источник схемы — это может быть URL работающего сервера, файл .graphql или несколько источников сразу:

schema:
  - 'src/schema/**/*.graphql'
  - 'http://localhost:4000/graphql':
      headers:
        Authorization: 'Bearer ${TOKEN}'

Добавьте команду в package.json:

{
  "scripts": {
    "codegen": "graphql-codegen",
    "codegen:watch": "graphql-codegen --watch"
  }
}

Структура сгенерированных файлов

После запуска npm run codegen инструмент создаёт типизированные хелперы. При использовании client-preset структура выглядит так:

src/
  gql/
    graphql.ts       # все сгенерированные типы
    gql.ts           # функция gql с поддержкой TypeScript
    index.ts         # реэкспорты

Пример сгенерированного содержимого graphql.ts:

// Этот файл генерируется автоматически — не редактируйте вручную

export type Maybe<T> = T | null;

export enum UserRole {
  Admin = 'ADMIN',
  User = 'USER',
  Guest = 'GUEST'
}

export type User = {
  __typename?: 'User';
  id: Scalars['ID']['output'];
  name: Scalars['String']['output'];
  email: Scalars['String']['output'];
  role: UserRole;
  createdAt: Scalars['DateTime']['output'];
};

export type GetUserQueryVariables = Exact<{
  id: Scalars['ID']['input'];
}>;

export type GetUserQuery = {
  __typename?: 'Query';
  user?: {
    __typename?: 'User';
    id: string;
    name: string;
    email: string;
    role: UserRole;
  } | null;
};

Обратите внимание: тип GetUserQuery содержит только те поля, которые запрашиваются в конкретной операции, а не все поля типа User. Это ключевое отличие от ручной типизации.

Написание типизированных операций

Опишите GraphQL-операции в отдельных .graphql файлах:

# src/features/users/queries.graphql

query GetUser($id: ID!) {
  user(id: $id) {
    id
    name
    email
    role
  }
}

query GetUsers($filter: UserFilter) {
  users(filter: $filter) {
    items {
      id
      name
      role
    }
    total
    hasNextPage
  }
}

mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    email
  }
}

После npm run codegen все эти операции получат точные TypeScript-типы.

Интеграция с Apollo Client

import { useQuery, useMutation } from '@apollo/client';
import { graphql } from '../../gql';

// graphql() — типизированная замена gql из Apollo
const GET_USER = graphql(`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
      role
    }
  }
`);

const UPDATE_USER = graphql(`
  mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
    updateUser(id: $id, input: $input) {
      id
      name
      email
    }
  }
`);

function UserProfile({ userId }: { userId: string }) {
  // data автоматически имеет тип GetUserQuery
  const { data, loading, error } = useQuery(GET_USER, {
    variables: { id: userId }, // id: number вызовет ошибку TypeScript
  });

  const [updateUser] = useMutation(UPDATE_USER);

  if (loading) return <div>Загрузка...</div>;
  if (error) return <div>Ошибка: {error.message}</div>;

  const user = data?.user;
  if (!user) return <div>Пользователь не найден</div>;

  const handleUpdate = async () => {
    await updateUser({
      variables: {
        id: user.id,
        input: {
          name: 'Новое имя',
          // TypeScript проверит структуру UpdateUserInput
        },
      },
    });
  };

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      {/* user.role — тип UserRole, не string */}
      <span>{user.role === 'ADMIN' ? 'Администратор' : 'Пользователь'}</span>
      <button onClick={handleUpdate}>Обновить</button>
    </div>
  );
}

Фрагменты и типизация переиспользуемых частей

Фрагменты — мощный инструмент GraphQL, и graphql-codegen генерирует для них отдельные типы:

# src/features/users/fragments.graphql

fragment UserBase on User {
  id
  name
  email
}

fragment UserWithRole on User {
  ...UserBase
  role
  createdAt
}
# Использование фрагмента в запросе
query GetAllUsers {
  users {
    items {
      ...UserWithRole
    }
    total
  }
}

Сгенерированные типы фрагментов можно использовать в компонентах напрямую:

import { FragmentType, useFragment, graphql } from '../../gql';

const USER_BASE_FRAGMENT = graphql(`
  fragment UserBase on User {
    id
    name
    email
  }
`);

interface UserCardProps {
  // FragmentType обеспечивает type-safe передачу фрагментов
  user: FragmentType<typeof USER_BASE_FRAGMENT>;
}

function UserCard({ user }: UserCardProps) {
  // useFragment «разворачивает» тип фрагмента
  const userData = useFragment(USER_BASE_FRAGMENT, user);

  return (
    <div>
      <strong>{userData.name}</strong>
      <span>{userData.email}</span>
    </div>
  );
}

Такой паттерн называют «colocated fragments» — каждый компонент сам описывает, какие данные ему нужны.

Кастомные скаляры

GraphQL позволяет определять собственные скалярные типы. По умолчанию graphql-codegen типизирует их как any. Переопределите это в конфигурации:

# codegen.yml
generates:
  src/gql/:
    preset: client
    config:
      scalars:
        DateTime: string
        Date: string
        UUID: string
        JSON: Record<string, unknown>
        BigInt: number
        Upload: File

Или используйте более строгие типы через пакет graphql-scalars:

npm install graphql-scalars
config:
  scalars:
    DateTime: 'Date'
    UUID: 'string & { __brand: "UUID" }'

Настройка для монорепозитория

В проектах с несколькими сервисами конфигурация может описывать несколько источников:

# codegen.yml
generates:
  # Типы для основного API
  src/gql/main/:
    schema: 'http://localhost:4000/graphql'
    documents: 'src/features/**/*.graphql'
    preset: client

  # Типы для микросервиса уведомлений
  src/gql/notifications/:
    schema: 'http://localhost:4001/graphql'
    documents: 'src/notifications/**/*.graphql'
    preset: client

Режим наблюдения в разработке

В процессе разработки удобно запускать кодогенерацию в режиме отслеживания изменений:

npm run codegen:watch

При каждом изменении .graphql файла или схемы типы пересоздаются автоматически. Это позволяет видеть ошибки типизации сразу после правки запроса.

Чтобы подключить кодогенерацию к общему dev-серверу, настройте concurrently:

{
  "scripts": {
    "dev": "concurrently \"next dev\" \"graphql-codegen --watch\""
  }
}

Валидация операций

graphql-codegen можно использовать не только для генерации типов, но и для валидации всех клиентских запросов относительно схемы:

npm install -D @graphql-codegen/schema-ast @graphql-inspector/cli
{
  "scripts": {
    "validate:graphql": "graphql-inspector validate src/**/*.graphql http://localhost:4000/graphql"
  }
}

Это позволяет поймать использование несуществующих полей или неправильных аргументов ещё на этапе CI, до деплоя.

Типизация без файлов .graphql

Если запросы определяются прямо в TypeScript-коде через теговые шаблонные строки, конфигурацию нужно скорректировать:

// Запросы прямо в .tsx файлах
const GET_POSTS = graphql(`
  query GetPosts($authorId: ID!) {
    posts(authorId: $authorId) {
      id
      title
      publishedAt
    }
  }
`);
# codegen.yml
documents:
  - 'src/**/*.tsx'
  - 'src/**/*.ts'
  - '!src/gql/**'  # исключаем уже сгенерированные файлы

graphql-codegen умеет извлекать операции прямо из TypeScript-файлов — достаточно указать правильный gqlTagName в конфигурации.

Практические рекомендации

Несколько правил, которые упрощают работу с типизированным GraphQL:

Никогда не редактируйте сгенерированные файлы. Добавьте директорию src/gql/ в .gitignore или коммитьте только результат — но никогда не правьте файлы вручную. Они перезаписываются при следующей генерации.

Используйте строгий режим TypeScript. В tsconfig.json включите "strict": true — это активирует проверки на null и undefined, которые критически важны для GraphQL, где многие поля могут быть nullable.

Разделяйте фрагменты по компонентам. Каждый компонент описывает свой фрагмент — это упрощает понимание зависимостей и уменьшает объём передаваемых данных.

Включайте кодогенерацию в CI. Добавьте npm run codegen перед шагом проверки типов — это гарантирует, что типы всегда актуальны относительно схемы.

# .github/workflows/ci.yml
- name: Generate GraphQL types
  run: npm run codegen
- name: Type check
  run: npx tsc --noEmit

Итог

Типизация GraphQL с помощью graphql-codegen переводит взаимодействие с API из режима «надейся, что данные придут правильные» в режим полной проверки на этапе компиляции. Схема сервера становится источником истины для клиентских типов, а любое изменение API немедленно отражается в TypeScript-ошибках.

Основной стек для старта:

  • @graphql-codegen/cli + @graphql-codegen/client-preset — генерация типов
  • graphql — основная библиотека
  • Apollo Client или urql — клиент с поддержкой типизированных операций
  • Режим --watch в разработке для мгновенной обратной связи

Освоить TypeScript глубоко и применять его в реальных проектах, включая работу с GraphQL, можно на курсе TypeScript на PurpleSchool.

Стрелочка влевоИспользование Union в TypeScriptТипы данных в TypeScriptСтрелочка вправо

Постройте личный план изучения Typescript до уровня Middle — бесплатно!

Typescript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Typescript

Переменные и константы в TypeScriptTypeScript: ключевое слово using и Explicit Resource ManagementНастройка типов с declare module в TypeScriptTypeScript типизация в Node.js проектеTypeScript ESLint — настройка линтера для проектаTypeScript 5: новые возможности и измененияПреобразование к типу в TypeScript (Type Assertion)Кортежи в TypeScripttsconfig.json — полный разбор настроек TypeScript компилятораTypeScript с tRPC — типобезопасный APITypeScript strict и strictNullChecks — строгий режимКак собрать TypeScript проект с RollupОбъекты в TypeScriptMonorepo с Turborepo и TypeScriptТип функции и стрелочные функции в TypeScriptФункции в TypeScriptТипизация ошибок и обработка исключений в TypeScriptTypeScript ключевое слово declareМассивы в TypeScript
Как правильно использовать тип UnknownИспользование Union в TypeScriptTypeScript с GraphQL — типизация и кодогенерацияТипы данных в TypeScriptType Guards в TypeScript — как работает сужение типовSymbols в TypeScriptСтруктурная типизация в TypeScriptNull и Undefined в TypeScriptТип never в TypeScriptСужение типов (Narrowing) в TypeScriptПонимание Literal Types. Ключевые аспекты и примеры использованияОператор infer в Typescript?Типы функций в TypeScript. Руководство для разработчиковTypeScript с Express.js: типизация маршрутов и middlewareEnums в TypeScriptДискриминированные объединения в TypeScriptСлужебный тип Awaited в TypeScriptКак типизировать массивы TypeScriptКак правильно использовать тип any
Открыть базу знаний

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

CSS Flexbox

Антон Ларичев
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
бесплатно
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий