Реализация GraphQL Code Generation (типизация клиента)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация GraphQL Code Generation (типизация клиента)
Средняя
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Кодогенерация и типизация GraphQL

GraphQL-схема — контракт между сервером и клиентом. GraphQL Code Generator автоматически создаёт TypeScript-типы из схемы и операций, исключая ручное поддержание синхронности типов и runtime-ошибки несоответствия данных.

Установка GraphQL Code Generator

npm install -D @graphql-codegen/cli @graphql-codegen/typescript \
  @graphql-codegen/typescript-resolvers \
  @graphql-codegen/typescript-operations \
  @graphql-codegen/typescript-react-apollo \
  @graphql-codegen/introspection

Конфигурация codegen.yml

# codegen.yml
overwrite: true
schema: "http://localhost:4000/graphql"  # или путь к SDL файлу
documents: "src/**/*.graphql"            # клиентские операции

generates:
  # Серверные типы (резолверы)
  src/generated/graphql-server.ts:
    plugins:
      - typescript
      - typescript-resolvers
    config:
      contextType: "../context#GraphQLContext"
      mappers:
        # Маппинг GraphQL-типов на реальные модели БД
        User: "../models/User#UserModel"
        Post: "../models/Post#PostModel"
      useIndexSignature: true
      enumsAsTypes: true
      avoidOptionals:
        field: true

  # Клиентские типы (операции + хуки)
  src/generated/graphql-client.ts:
    plugins:
      - typescript
      - typescript-operations
      - typescript-react-apollo
    config:
      withHooks: true
      withComponent: false
      withHOC: false
      dedupeFragments: true

  # Introspection для IDE
  src/generated/introspection.json:
    plugins:
      - introspection

Серверные типы резолверов

После генерации типы Resolvers точно соответствуют схеме:

// src/generated/graphql-server.ts (фрагмент)
export type QueryResolvers<ContextType = GraphQLContext> = {
  user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<QueryUserArgs, 'id'>>;
  posts?: Resolver<ResolversTypes['PostConnection'], ParentType, ContextType, Partial<QueryPostsArgs>>;
}

export type UserResolvers<ContextType = GraphQLContext> = {
  id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
  name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
  posts?: Resolver<Array<ResolversTypes['Post']>, ParentType, ContextType>;
  __isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
}
// src/resolvers/user.resolver.ts
import { QueryResolvers, UserResolvers } from '../generated/graphql-server'
import { GraphQLContext } from '../context'

export const userQueryResolvers: QueryResolvers = {
  user: async (parent, { id }, ctx: GraphQLContext) => {
    // TypeScript знает: id: string, возврат: UserModel | null
    return ctx.db.users.findById(id)
  }
}

export const userTypeResolvers: UserResolvers = {
  posts: async (parent, args, ctx) => {
    // parent типизирован как UserModel (из mappers)
    return ctx.loaders.postsByUserId.load(parent.id)
  }
}

// Собранные резолверы с полной типизацией
export const resolvers = {
  Query: userQueryResolvers,
  User: userTypeResolvers,
}

Клиентские операции с хуками

# src/operations/posts.graphql
query GetPosts($limit: Int, $after: String) {
  posts(first: $limit, after: $after) {
    edges {
      cursor
      node {
        id
        title
        author {
          id
          name
        }
        createdAt
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    slug
  }
}
// После codegen доступны типизированные хуки:
import { useGetPostsQuery, useCreatePostMutation } from './generated/graphql-client'

function PostList() {
  const { data, loading, fetchMore } = useGetPostsQuery({
    variables: { limit: 20 }
  })

  // data.posts.edges — полностью типизировано
  // TypeScript выведет ошибку при обращении к несуществующим полям

  const [createPost] = useCreatePostMutation({
    onCompleted: (data) => {
      // data.createPost.id — типизировано
      console.log('Created:', data.createPost.id)
    }
  })

  return (
    <div>
      {data?.posts.edges.map(({ node, cursor }) => (
        <PostCard key={cursor} post={node} />
      ))}
    </div>
  )
}

Фрагменты и Fragment Masking

# src/operations/fragments.graphql
fragment UserAvatar on User {
  id
  name
  avatarUrl
}

fragment PostCard on Post {
  id
  title
  excerpt
  author {
    ...UserAvatar
  }
}
// Fragment Masking: компонент получает только те данные, которые запросил
import { FragmentType, useFragment } from './generated/fragment-masking'
import { PostCardFragmentDoc } from './generated/graphql-client'

function PostCard({ post }: { post: FragmentType<typeof PostCardFragmentDoc> }) {
  const data = useFragment(PostCardFragmentDoc, post)
  // data типизирован как PostCardFragment — только запрошенные поля
}

Автоматизация в CI/CD

// package.json
{
  "scripts": {
    "codegen": "graphql-codegen --config codegen.yml",
    "codegen:watch": "graphql-codegen --config codegen.yml --watch",
    "type-check": "tsc --noEmit",
    "lint": "eslint src --ext .ts,.tsx"
  }
}
# .github/workflows/codegen-check.yml
name: GraphQL Codegen Check
on: [pull_request]

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - name: Start GraphQL server
        run: npm run dev &
      - name: Wait for server
        run: npx wait-on http://localhost:4000/graphql
      - name: Run codegen
        run: npm run codegen
      - name: Check for uncommitted changes
        run: |
          if [[ -n $(git diff --name-only) ]]; then
            echo "Generated files are out of sync. Run npm run codegen."
            git diff
            exit 1
          fi
      - name: TypeScript check
        run: npm run type-check

Серверные валидационные утилиты

// Кодоген создаёт типы входных данных для мутаций
import { CreatePostInput, MutationCreatePostArgs } from './generated/graphql-server'
import { z } from 'zod'

// Zod-схема на базе сгенерированного типа
const createPostSchema = z.object({
  title: z.string().min(1).max(200),
  content: z.string().min(10),
  tags: z.array(z.string()).max(10).optional(),
} satisfies Record<keyof CreatePostInput, z.ZodTypeAny>)

// TypeScript проверит, что схема покрывает все поля типа

Срок выполнения

Настройка GraphQL Code Generator для серверных резолверов и клиентских хуков с CI-проверкой синхронности — 1–2 рабочих дня.