ДокументацияОсновы вебаGraphQL: запросы, мутации, подписки, когда использовать
Средний 15 мин чтения

GraphQL: запросы, мутации, подписки, когда использовать

GraphQL — язык запросов для API. Запросы, мутации, подписки, схема, резолверы, сравнение с REST, Apollo Client и когда переходить на GraphQL.

graphqlapiqueriesmutationssubscriptionsapollo

Что такое GraphQL

GraphQL — язык запросов для API, созданный Facebook в 2015 году. В отличие от REST, где вы делаете запрос к конкретному эндпоинту и получаете фиксированный набор данных, GraphQL позволяет клиенту указать ровно те поля, которые нужны.

Проблемы REST, которые решает GraphQL:

  • Over-fetching — REST возвращает все поля, даже если нужны 3 из 20
  • Under-fetching — для связанных данных нужно делать несколько запросов
  • Множество эндпоинтов/users, /users/1/posts, /users/1/comments

GraphQL: один эндпоинт, один запрос — все нужные данные.

REST:
  GET /users/1              → { id, name, email, phone, address, company, ... }
  GET /users/1/posts        → [{ id, title, body, ... }]
  GET /users/1/followers    → [{ id, name, ... }]

GraphQL:
  query {
    user(id: 1) {
      name
      posts(limit: 5) { title }
      followers { name }
    }
  }
  → { name: "Анна", posts: [{ title: "..." }], followers: [{ name: "..." }] }

Основные понятия

ПонятиеОписание
QueryПолучение данных (аналог GET)
MutationИзменение данных (аналог POST/PUT/DELETE)
SubscriptionПолучение данных в реальном времени (WebSocket)
SchemaОписание типов и связей (контракт API)
ResolverФункция, которая возвращает данные для поля

Schema (Схема)

Схема описывает все типы данных и операции API:

type User {
  id: ID!
  name: String!
  email: String!
  avatar: String
  posts(limit: Int): [Post!]!
  followers: [User!]!
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
  comments: [Comment!]!
  createdAt: String!
}

type Comment {
  id: ID!
  text: String!
  author: User!
}

type Query {
  user(id: ID!): User
  users(page: Int, limit: Int): [User!]!
  post(id: ID!): Post
  posts(filter: PostFilter): [Post!]!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
  updatePost(id: ID!, input: UpdatePostInput!): Post!
  deletePost(id: ID!): Boolean!
  login(email: String!, password: String!): AuthPayload!
}

type Subscription {
  newPost: Post!
  newComment(postId: ID!): Comment!
}

input CreatePostInput {
  title: String!
  body: String!
}

input UpdatePostInput {
  title: String
  body: String
}

input PostFilter {
  authorId: ID
  search: String
}

type AuthPayload {
  token: String!
  user: User!
}

Символы:

  • ! — поле обязательно (non-null)
  • [Type!]! — обязательный массив обязательных элементов
  • ID — уникальный идентификатор (строка)
  • Input — тип для входных данных

Queries (Запросы)

Простой запрос

query {
  user(id: 1) {
    name
    email
  }
}

Ответ:

{
  "data": {
    "user": {
      "name": "Анна",
      "email": "anna@example.com"
    }
  }
}

Связанные данные (один запрос вместо трёх в REST)

query {
  user(id: 1) {
    name
    avatar
    posts(limit: 5) {
      title
      createdAt
      comments {
        text
        author { name }
      }
    }
  }
}

Несколько запросов за один раз

query {
  user(id: 1) { name email }
  posts(limit: 10) { title author { name } }
  categories { id name }
}

Переменные

query GetUser($userId: ID!, $postsLimit: Int = 5) {
  user(id: $userId) {
    name
    posts(limit: $postsLimit) { title }
  }
}

Variables:

{
  "userId": 1,
  "postsLimit": 10
}

Фрагменты

Повторяющиеся наборы полей можно вынести:

fragment UserFields on User {
  id
  name
  email
  avatar
}

query {
  user(id: 1) {
    ...UserFields
    posts { title }
  }
  currentUser {
    ...UserFields
  }
}

Aliases

Когда нужно запросить одно поле с разными параметрами:

query {
  allPosts: posts(status: "published") { title }
  draftPosts: posts(status: "draft") { title }
}

Mutations (Мутации)

Мутации изменяют данные:

mutation CreatePost($input: CreatePostInput!) {
  createPost(input: $input) {
    id
    title
    body
    createdAt
  }
}

Variables:

{
  "input": {
    "title": "Мой первый пост",
    "body": "Привет, мир!"
  }
}

Обновление

mutation UpdatePost($id: ID!, $input: UpdatePostInput!) {
  updatePost(id: $id, input: $input) {
    id
    title
    body
  }
}
{
  "id": 42,
  "input": { "title": "Обновлённый заголовок" }
}

Удаление

mutation DeletePost($id: ID!) {
  deletePost(id: $id)
}

Ответ: { "data": { "deletePost": true } }

Subscriptions (Подписки)

Подписки работают через WebSocket — данные приходят в реальном времени:

subscription OnNewComment($postId: ID!) {
  newComment(postId: $postId) {
    id
    text
    author { name avatar }
    createdAt
  }
}

Каждый новый комментарий к посту будет приходить автоматически:

{
  "data": {
    "newComment": {
      "id": 99,
      "text": "Отличная статья!",
      "author": { "name": "Иван", "avatar": "..." },
      "createdAt": "2025-01-15T10:30:00Z"
    }
  }
}

Применение: чаты, уведомления, лента, collaborative editing.

GraphQL на фронтенде

fetch (без библиотек)

async function graphql<T>(query: string, variables?: Record<string, unknown>): Promise<T> {
  const response = await fetch('/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`,
    },
    body: JSON.stringify({ query, variables }),
  })

  const { data, errors } = await response.json()

  if (errors) {
    throw new Error(errors[0].message)
  }

  return data
}

// Использование
const data = await graphql<{ user: User }>(`
  query GetUser($id: ID!) {
    user(id: $id) { name email posts { title } }
  }
`, { id: 1 })

Apollo Client (React)

npm install @apollo/client graphql
import { ApolloClient, InMemoryCache, gql, useQuery } from '@apollo/client'

const client = new ApolloClient({
  uri: '/graphql',
  cache: new InMemoryCache(),
  headers: { authorization: `Bearer ${token}` },
})

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts { title createdAt }
    }
  }
`

function UserProfile({ userId }: { userId: string }) {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { id: userId },
  })

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

  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>{data.user.email}</p>
      {data.user.posts.map((post: any) => (
        <article key={post.title}>{post.title}</article>
      ))}
    </div>
  )
}

Vue + Vue Apollo

npm install @vue/apollo-composable graphql
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import gql from 'graphql-tag'

const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { name email posts { title } }
  }
`

const userId = ref('1')
const { result, loading, error } = useQuery(GET_USER, { id: userId })
</script>

<template>
  <div v-if="loading">Загрузка...</div>
  <div v-else-if="error">Ошибка: {{ error.message }}</div>
  <div v-else>
    <h1>{{ result.user.name }}</h1>
    <p>{{ result.user.email }}</p>
  </div>
</template>

Сравнение REST и GraphQL

КритерийRESTGraphQL
ЭндпоинтыМного (/users, /posts)Один (/graphql)
ДанныеФиксированный набор полейКлиент выбирает поля
Количество запросовN запросов для N ресурсовОдин запрос
Over-fetchingЧастоНет
Under-fetchingЧастоНет
КэшированиеHTTP-кэш (просто)Нужен Apollo Cache
Файловая загрузкаПросто (multipart)Сложнее
ОтладкаПростая (curl, Postman)Нужен GraphiQL / Playground
Кривая обученияНизкаяСредняя
ИнструментыСтандартныеGraphiQL, Apollo DevTools

Когда использовать GraphQL

Подходит для:

  • Сложные данные с множеством связей (соцсети, e-commerce)
  • Мобильные приложения (экономия трафика)
  • Когда фронтенд-команда хочет независимости от бэкенда
  • Real-time функции (subscriptions)
  • Микросервисная архитектура (GraphQL как агрегатор)

Не подходит для:

  • Простых CRUD-приложений (REST проще)
  • Кэширование файлов (CDN для REST проще)
  • Команд, где никто не знает GraphQL
  • Простых API с 5–10 эндпоинтами

Инструменты

GraphiQL / GraphQL Playground

Интерактивная среда для тестирования запросов. Встроена во многие GraphQL-серверы:

  • Автодополнение на основе схемы
  • Документация типов
  • История запросов

Apollo Explorer

Облачный инструмент от Apollo с визуальным построением запросов.

Apollo Client DevTools

Расширение для Chrome — показывает кэш, запросы и мутации.

Best Practices

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

fragment PostCard on Post {
  id
  title
  createdAt
  author { name avatar }
}

DataLoader для N+1 проблемы

Если у поста есть author и резолвер делает запрос к БД на каждого автора — это N+1 запрос. DataLoader группирует их в один batch-запрос.

Persisted Queries

Вместо отправки полного текста запроса отправляется хеш:

POST /graphql
{ "extensions": { "persistedQuery": { "sha256Hash": "abc123..." } } }

Экономит трафик и улучшает безопасность.

Pagination (Relay-style)

query {
  posts(first: 10, after: "cursor123") {
    edges {
      node { id title }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Итог

  • GraphQL — один эндпоинт, клиент выбирает нужные поля
  • Query — чтение, Mutation — запись, Subscription — real-time
  • Schema определяет типы, resolver'ы возвращают данные
  • Решает over-fetching и under-fetching
  • Apollo Client — стандартная библиотека для React и Vue
  • Используйте для сложных данных, экономии трафика, real-time
  • REST проще для CRUD-приложений — выбирайте по задаче