LEARN X · ЗА 14 МИН

GraphQL

GraphQL за 14 минут: запросы, аргументы, фрагменты, переменные, мутации, схема, типы, input, директивы — всё в одном файле с комментариями.

GraphQL — язык запросов к API: клиент сам описывает, какие поля ему нужны, и получает ровно их одним запросом к единому endpoint. Ниже — весь язык на одной странице через закомментированный код.

Что такое GraphQL

В отличие от REST с десятком URL, у GraphQL один endpoint, а форму ответа задаёт сам клиент.

# Комментарии в GraphQL начинаются с решётки #
#
# REST:  GET /users/1, GET /users/1/posts, GET /posts/5/comments ...
#        несколько запросов, фиксированный набор полей, over-fetching.
#
# GraphQL: ОДИН endpoint (обычно POST /graphql), клиент описывает,
#          какие поля нужны — и получает РОВНО их, без лишнего.
#
# Ответ всегда JSON, его структура повторяет структуру запроса:
#   запрос  { user { name } }
#   ответ   { "data": { "user": { "name": "Аня" } } }

Запросы (query)

Операция чтения: перечисляем нужные поля, в том числе вложенные объекты.

# query — операция чтения данных (аналог GET).
# Слово query можно опустить, если в документе одна операция.
query {
  user {           # поле верхнего уровня
    id             # выбираем скалярные поля
    name
    email
    posts {        # вложенный объект — снова перечисляем его поля
      title
      published
    }
  }
}

# Нельзя "просто запросить объект" — у объектных полей
# ОБЯЗАТЕЛЬНО указываем, какие подполя нужны (user { ... }).

Аргументы

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

query {
  # Аргументы пишутся в скобках после имени поля.
  user(id: "1") {
    name

    # Аргументы есть и у вложенных полей: фильтрация, лимит, сортировка.
    posts(first: 3, status: PUBLISHED) {
      title
    }
  }

  # Поиск с фильтром
  search(query: "graphql", limit: 10) {
    title
  }
}

Псевдонимы и фрагменты

Alias переименовывает поле в ответе, fragment выносит повторяющийся набор полей.

query {
  # Псевдоним (alias): два раза одно поле с разными аргументами —
  # имя_в_ответе: имя_поля(...)
  active: user(id: "1") { ...userCard }
  admin:  user(id: "2") { ...userCard }
}

# Фрагмент — переиспользуемый набор полей для типа User.
fragment userCard on User {
  id
  name
  email
}

# Ответ: { "data": { "active": {...}, "admin": {...} } }

Переменные

Параметры запроса выносятся в типизированные переменные — запрос становится статичным.

# Переменные объявляются в скобках после имени операции: $имя: Тип
# Тип обязателен. Знак ! означает non-null (см. ниже).
query GetUser($id: ID!, $count: Int = 5) {
  user(id: $id) {            # подставляем переменную вместо литерала
    name
    posts(first: $count) {   # $count со значением по умолчанию 5
      title
    }
  }
}

# Значения переменных передаются отдельным JSON:
# { "id": "1", "count": 3 }

Мутации (mutation)

Операция записи: создание, изменение, удаление данных.

# mutation — операция изменения данных (аналог POST/PUT/DELETE).
mutation CreatePost($title: String!, $body: String!) {
  createPost(title: $title, body: $body) {
    # Мутация тоже возвращает данные — выбираем, что получить обратно:
    id
    title
    createdAt
  }
}

# Несколько мутаций в одной операции выполняются ПОСЛЕДОВАТЕЛЬНО
# (поля query — параллельно, поля mutation — по порядку, сверху вниз).

Подписки (subscription)

Поток событий от сервера в реальном времени (обычно через WebSocket).

# subscription — сервер сам шлёт данные клиенту при наступлении события.
subscription OnNewComment($postId: ID!) {
  commentAdded(postId: $postId) {
    id
    text
    author { name }
  }
}
# Клиент получает новое сообщение каждый раз, когда добавлен комментарий.

Схема и типы

Схема — контракт API: описывает типы, их поля и точки входа Query/Mutation.

# Схема пишется на SDL (Schema Definition Language).
# type определяет объектный тип и его поля: имя: Тип
type User {
  id: ID          # ID — уникальный идентификатор (сериализуется как строка)
  name: String    # String — текст (UTF-8)
  age: Int        # Int   — целое число
  rating: Float   # Float — число с плавающей точкой
  isAdmin: Boolean# Boolean — true / false
}

# Query — особый тип: корневые точки входа для чтения.
type Query {
  user(id: ID!): User
  users: [User]
}

Модификаторы типов

Знак ! делает значение обязательным, квадратные скобки описывают список.

type Post {
  id: ID!            # ! — non-null: поле не может быть null
  title: String!     # обязательная строка

  tags: [String!]    # список строк; сам список может быть null,
                     # но элементы внутри — не null

  comments: [Comment!]!  # non-null список non-null элементов:
                         # всегда массив (пусть и пустой [])
}

# Памятка по комбинациям:
#   [Type]    — список может быть null, элементы могут быть null
#   [Type!]   — список может быть null, элементы НЕ null
#   [Type]!   — список НЕ null, элементы могут быть null
#   [Type!]!  — список НЕ null и элементы НЕ null

Аргументы и input-типы

enum ограничивает значение списком вариантов, input группирует аргументы мутации.

# enum — перечисление: значение из фиксированного набора.
enum Role {
  ADMIN
  EDITOR
  VIEWER
}

# input — объект, передаваемый КАК АРГУМЕНТ (в отличие от type для ответа).
input CreateUserInput {
  name: String!
  role: Role = VIEWER   # значение по умолчанию
}

type Mutation {
  # Аргумент-объект удобнее длинного списка отдельных аргументов.
  createUser(data: CreateUserInput!): User!
}

Резолверы

Функции на сервере, которые «достают» значение для каждого поля схемы.

# Схема описывает ЧТО есть, резолверы — КАК это получить.
# На каждое поле — функция (resolver), возвращающая его значение.
#
# Пример на JavaScript (вне SDL, для понимания):
#
#   const resolvers = {
#     Query: {
#       user: (parent, args, context) => db.findUser(args.id),
#     },
#     User: {
#       posts: (user) => db.postsByAuthor(user.id),
#     },
#   };
#
# parent — результат родительского поля, args — аргументы поля,
# context — общие данные запроса (текущий пользователь, БД и т.п.).

Директивы

@include и @skip управляют тем, попадёт ли поле в ответ, по условию.

query GetUser($withEmail: Boolean!, $isPreview: Boolean!) {
  user(id: "1") {
    name

    # @include(if: ...) — включить поле, только если условие true
    email @include(if: $withEmail)

    # @skip(if: ...) — пропустить поле, если условие true
    secret @skip(if: $isPreview)
  }
}

# Директивы начинаются с @ и меняют поведение выполнения.
# Бывают и в схеме, например @deprecated(reason: "...") — пометка устаревшего поля.
Поддержать проект