Настройка Apollo Client для GraphQL в веб-приложении

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка Apollo Client для GraphQL в веб-приложении
Средняя
от 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

Настройка Apollo Client для GraphQL в веб-приложении

Apollo Client — полноценный GraphQL-клиент с нормализованным кэшем, реактивными запросами, мутациями, подписками и автогенерацией типов. Кэш хранит данные по идентификаторам объектов — обновление одной записи автоматически обновляет все компоненты, которые её используют.

Подходит для React, Vue, Angular — работает как отдельная библиотека. Особенно выигрывает в приложениях с большим числом взаимосвязанных объектов.

Что входит в работу

Установка и конфигурация Apollo Client, настройка HTTP и WebSocket link, аутентификация, кэш-политики, кодогенерация типов из схемы GraphQL, хуки для queries/mutations/subscriptions, обработка ошибок, DevTools.

Установка

npm install @apollo/client graphql
# для WebSocket
npm install graphql-ws

Конфигурация клиента

// lib/apollo/client.ts
import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  from,
  ApolloLink,
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient as createWsClient } from 'graphql-ws'
import { getMainDefinition } from '@apollo/client/utilities'
import { split } from '@apollo/client'

const httpLink = createHttpLink({
  uri: import.meta.env.VITE_GRAPHQL_URL ?? '/graphql',
})

// добавляем Authorization header к каждому запросу
const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token')
  return {
    headers: {
      ...headers,
      ...(token ? { authorization: `Bearer ${token}` } : {}),
    },
  }
})

// обработка ошибок
const errorLink = onError(({ graphQLErrors, networkError }) => {
  if (graphQLErrors) {
    for (const { message, locations, path, extensions } of graphQLErrors) {
      console.error(`[GraphQL error]: ${message}`, { locations, path })
      if (extensions?.code === 'UNAUTHENTICATED') {
        authStore.logout()
      }
    }
  }
  if (networkError) {
    console.error('[Network error]', networkError)
  }
})

// WebSocket для подписок
const wsLink = new GraphQLWsLink(
  createWsClient({
    url: import.meta.env.VITE_GRAPHQL_WS_URL ?? 'ws://localhost:4000/graphql',
    connectionParams: () => ({
      authorization: `Bearer ${localStorage.getItem('token')}`,
    }),
  })
)

// split: subscriptions → WS, остальное → HTTP
const splitLink = split(
  ({ query }) => {
    const def = getMainDefinition(query)
    return def.kind === 'OperationDefinition' && def.operation === 'subscription'
  },
  wsLink,
  from([errorLink, authLink, httpLink])
)

export const apolloClient = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache({
    typePolicies: {
      Product: {
        keyFields: ['id'],
      },
      // пагинация
      Query: {
        fields: {
          products: {
            keyArgs: ['categoryId', 'search'],
            merge(existing, incoming, { args }) {
              if (!args?.offset) return incoming
              return {
                ...incoming,
                items: [...(existing?.items ?? []), ...incoming.items],
              }
            },
          },
        },
      },
    },
  }),
  defaultOptions: {
    watchQuery: {
      fetchPolicy: 'cache-and-network',
      errorPolicy: 'all',
    },
    query: {
      fetchPolicy: 'network-only',
      errorPolicy: 'all',
    },
  },
})
// main.tsx
import { ApolloProvider } from '@apollo/client'
import { apolloClient } from '@/lib/apollo/client'

function App() {
  return (
    <ApolloProvider client={apolloClient}>
      <Router />
    </ApolloProvider>
  )
}

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

npm install -D @graphql-codegen/cli @graphql-codegen/client-preset
npx graphql-codegen init
# codegen.ts или codegen.yml
import type { CodegenConfig } from '@graphql-codegen/cli'

const config: CodegenConfig = {
  overwrite: true,
  schema: 'http://localhost:4000/graphql',
  documents: 'src/**/*.graphql',
  generates: {
    'src/gql/': {
      preset: 'client',
      config: {
        useTypeImports: true,
        strictScalars: true,
        scalars: {
          DateTime: 'string',
          UUID: 'string',
        },
      },
    },
  },
}

export default config
npx graphql-codegen --watch  # в dev
npx graphql-codegen          # в CI

GraphQL-документы

# src/features/products/products.graphql

query GetProducts($categoryId: ID!, $search: String, $offset: Int, $limit: Int) {
  products(categoryId: $categoryId, search: $search, offset: $offset, limit: $limit) {
    items {
      id
      name
      price
      stock
      category {
        id
        name
      }
    }
    total
    hasMore
  }
}

query GetProduct($id: ID!) {
  product(id: $id) {
    id
    name
    description
    price
    stock
    images { url alt }
    category { id name }
  }
}

mutation CreateProduct($input: CreateProductInput!) {
  createProduct(input: $input) {
    id
    name
    price
  }
}

mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {
  updateProduct(id: $id, input: $input) {
    id
    name
    price
    stock
  }
}

subscription OnStockUpdate($productId: ID!) {
  stockUpdated(productId: $productId) {
    productId
    stock
  }
}

Хуки после кодогенерации

// features/products/useProducts.ts
import { useQuery, useMutation, useSubscription } from '@apollo/client'
import {
  GetProductsDocument,
  GetProductDocument,
  CreateProductDocument,
  UpdateProductDocument,
  OnStockUpdateDocument,
} from '@/gql/graphql'

export function useProducts(categoryId: string) {
  return useQuery(GetProductsDocument, {
    variables: { categoryId },
    notifyOnNetworkStatusChange: true,
  })
}

export function useProduct(id: string) {
  return useQuery(GetProductDocument, {
    variables: { id },
    skip: !id,
  })
}

export function useCreateProduct() {
  return useMutation(CreateProductDocument, {
    update(cache, { data }) {
      if (!data?.createProduct) return
      cache.modify({
        fields: {
          products(existing) {
            return {
              ...existing,
              items: [data.createProduct, ...existing.items],
              total: existing.total + 1,
            }
          },
        },
      })
    },
  })
}

export function useStockSubscription(productId: string) {
  return useSubscription(OnStockUpdateDocument, {
    variables: { productId },
    onData: ({ client, data }) => {
      const update = data.data?.stockUpdated
      if (!update) return
      client.cache.modify({
        id: client.cache.identify({ __typename: 'Product', id: update.productId }),
        fields: {
          stock: () => update.stock,
        },
      })
    },
  })
}

Использование в компоненте

function ProductList({ categoryId }: { categoryId: string }) {
  const { data, loading, error, fetchMore, networkStatus } = useProducts(categoryId)
  const [createProduct, { loading: creating }] = useCreateProduct()

  if (loading && networkStatus !== NetworkStatus.fetchMore) return <Skeleton />
  if (error) return <ErrorMessage error={error} />

  const products = data?.products.items ?? []
  const hasMore = data?.products.hasMore ?? false

  return (
    <>
      <ProductGrid products={products} />
      {hasMore && (
        <button
          onClick={() => fetchMore({ variables: { offset: products.length } })}
          disabled={networkStatus === NetworkStatus.fetchMore}
        >
          Загрузить ещё
        </button>
      )}
    </>
  )
}

Фрагменты для переиспользования

# fragments.graphql
fragment ProductBasic on Product {
  id
  name
  price
  stock
}

fragment ProductFull on Product {
  ...ProductBasic
  description
  images { url alt }
  category { id name }
}
import { useFragment } from '@apollo/client'
import { ProductBasicFragmentDoc } from '@/gql/graphql'

// читаем фрагмент из кэша без отдельного запроса
function ProductCardFromCache({ id }: { id: string }) {
  const { data } = useFragment({
    fragment: ProductBasicFragmentDoc,
    from: { __typename: 'Product', id },
  })

  return <div>{data?.name}</div>
}

Apollo DevTools

Расширение для Chrome/Firefox — визуализация кэша, просмотр всех выполненных запросов, мутаций и подписок, explorer для произвольных GraphQL-запросов прямо из браузера.

Что делаем

Настраиваем Apollo Client с HTTP + WS link, аутентификацией и обработкой ошибок, конфигурируем кодогенерацию типов, пишем запросы/мутации/подписки в .graphql файлах, настраиваем политики кэша для пагинированных коллекций, покрываем тестами через MockedProvider.

Срок: 3–5 дней, включая настройку кодогенерации и написание хуков для всех entities.