Налаштування Apollo Client для GraphQL у веб-застосунку

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Apollo Client для GraphQL у веб-застосунку
Середня
від 1 робочого дня до 3 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1260
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    830
  • 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, хуки для запитів/мутацій/підписок, обробка помилок, 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.