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: "...") — пометка устаревшего поля.