Схемы GraphQL Записывать, форматировать и понимать SDL без шаблонного кода
Схемы GraphQL — это самодокументируемые контракты между сервером и клиентом. В этом руководстве вы познакомитесь с языком определения схем (SDL) с нуля — типами, скалярными типами, запросами, мутациями, типами входных данных, перечислениями и интерфейсами — чтобы уверенно читать и писать схемы.
Если вы работали с REST-API и впервые открыли схему GraphQL, синтаксис, вероятно, показался знакомым, но не вполне распознаваемым. Это нормально. Язык определения схемы (SDL) краток, но в каждой строке несёт много смысла. В этой статье мы разбираем его подробно, чтобы вы могли читать и писать схемы, не обращаясь к документации каждые пять минут.
Что такое схема GraphQL на самом деле
Схема GraphQL — это контракт между вашим сервером API и каждым клиентом, обращающимся к нему. Она определяет, какая именно данные существует, какие операции доступны и каковы формы ответов. В отличие от REST — где вы узнаёте о конечных точках через документацию, пробу и ошибки — GraphQL делает контракт явным и машинопонятным.
Каждый API на GraphQL начинается с схемы, написанной на языке SDL. Сервер проверяет все запросы на соответствие этой схеме во время выполнения. Если клиент запрашивает поле, которое не существует в схеме, запрос отклоняется до запуска резолвера. Это основное преимущество системы типов GraphQL: схема является истиной для поверхности вашего API.
Основы SDL: типы, запросы и мутации
Каждый файл SDL строится на основе определений типов. Вот самый простой полезный пример схемы:
type Query {
hello: String
}
Query является точкой входа для операций чтения. Каждая схема должна иметь одну. Поле hello возвращает String — один из пяти встроенных типов скаляров.
Пять встроенных скаляров:
- String — текст на UTF-8
- Int — 32-битное целое число с знаком
- Float — двойная точность с плавающей точкой
- Boolean — истинно или ложно
- ИДЕНТИФИКАТОР — уникальный идентификатор, сериализуемый как строка
Реальные схемы определяют пользовательские типы объектов. Вот более типичный паттерн:
type User {
id: ID!
name: String!
email: String!
role: UserRole!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String
author: User!
publishedAt: DateTime
}
type Query {
user(id: ID!): User
posts: [Post!]!
}
В этих 20 строках происходит многое. Давайте разберёмся.
Непустые поля и списки
The ! после типа означает, что поле не может быть пустым — оно никогда не возвращает null. Без этого поля являются необязательными, и клиент должен обрабатывать отсутствующее значение. Это различие важно на уровне выполнения: если резолвер необязательного поля выбрасывает исключение или возвращает null, GraphQL передаст null вверх по дереву, что может привести к отсутствию родительских полей.
Списки используют квадратные скобки: [Post] является необязательным списком необязательных постов. [Post!]! является необязательным списком необязательных постов. Это версия, которую вы обычно предпочитаете — поле, которое всегда возвращает массив (возможно, пустой), и каждый элемент в нём всегда является реальным объектом, а не null.
Четыре комбинации, раскрытые:
[Post]— необязательный список, необязательные элементы (редко полезны)[Post!]— необязательный список, необязательные элементы[Post]!— необязательный список, необязательные элементы[Post!]!— необязательный список, необязательные элементы (наиболее распространённый)
Мутации: запись данных
Запросы являются только для чтения. Мутации обрабатывают записи. Тип Mutation строится так же, как и Query, но по соглашению его поля имеют побочные эффекты:
type Mutation {
createPost(input: CreatePostInput!): Post!
deletePost(id: ID!): Boolean!
}
Уведомление CreatePostInput! — это тип ввода, который ведёт прямо к следующему понятию.
Типы ввода против типов вывода
Это одна из наиболее распространённых причин путаницы в схемах GraphQL. Вы не может переиспользуете объектный тип, такой как Post , как аргумент в мутации. SDL имеет два отдельных типа для этого по причине:
- Типы объектов (
type) — используются в ответах. Могут содержать резолверы и сложную логику полей. - Типы ввода (
input) — используются в аргументах. Простые данные, без резолверов. Поля могут ссылаться только на скаляры или другие типы ввода.
input CreatePostInput {
title: String!
body: String
authorId: ID!
}
Разделение обеспечивает чистую проверку аргументов и предотвращает возникновение циклических ссылок, которые возникнут, если использовать типы вывода (которые могут ссылаться друг на друга в сложных графах) напрямую в качестве аргументов.
Пользовательские скаляры
Пять встроенных скаляров не охватывают всё. Дата-время, URL, блок JSON — эти типы требуют пользовательских скаляров. Вы объявляете их так:
scalar DateTime
scalar JSON
scalar URL
Объявление скаляра устанавливает имя, которое будет использоваться. Реальная логика сериализации, десериализации и проверки происходит на уровне сервера — не в файле схемы. Библиотеки, такие как graphql-scalars предоставляют готовые реализации для распространённых типов, чтобы вы не писали их с нуля.
Используйте пользовательские скаляры, когда String по техническим причинам правильны, но семантически ошибочны. Поле, типированное как DateTime сообщает нам намерение; поле, типированное как String заставляет клиента угадывать формат.
Энумы
Энумы ограничивают поле фиксированным набором строковых значений. Они более надёжны, чем простые строки, потому что схема обеспечивает разрешение допустимых значений на уровне типа:
enum UserRole {
ADMIN
EDITOR
VIEWER
}
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
Поле, типированное как UserRole никогда не возвращает неожиданную строку. Поле, типированное как String может возвращать любое значение. Используйте энумы каждый раз, когда набор значений известен, стабилен и имеет смысл для клиентов — это делает интроспекцию схемы значительно более полезной.
Интерфейсы и объединения
Когда поле может возвращать разные типы объектов, GraphQL предлагает два механизма: интерфейсы и объединения.
Интерфейсы определяют совокупность общих полей. Типы, реализующие интерфейс, должны включать эти поля:
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
type Post implements Node {
id: ID!
title: String!
}
Объединения группируют типы без необходимости иметь общие поля. Они полезны, когда возможные типы не имеют никаких структурных сходств:
union SearchResult = User | Post | Comment
type Query {
search(query: String!): [SearchResult!]!
}
Клиенты используют встроенные фрагменты для обработки каждого типа в объединении или интерфейсе: ... on User { name }. Если вы возвращаете полиморфные данные, предпочитайте интерфейсы, когда типы имеют общие поля, и объединения, когда они не имеют.
Директивы
Директивы изменяют поведение на уровне поля или типа. Две из них встроены в каждый GraphQL-реализации:
@deprecated(reason: "Use newField instead")— помечает поле как устаревшее в интроспекции@skip(if: Boolean)и@include(if: Boolean)— условные выражения на клиентской стороне в запросах
Вы также можете определять пользовательские директивы для таких вещей, как проверка аутентификации, подсказки по ограничению скорости или аннотации кэширования. Они объявляются в схеме и реализуются на сервере:
directive @auth(requires: UserRole = ADMIN) on FIELD_DEFINITION
type Mutation {
deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
}
GraphQL против REST: в каких случаях использовать?
Контракт-первый подход GraphQL приносит пользу в определённых ситуациях. Он является правильным выбором, когда:
- Множество клиентов (веб-сайт, мобильное приложение, сторонние сервисы) нуждаются в разных подмножествах одной и той же информации
- Вы хотите развивать API без версий — добавлять поля свободно, устаревать, а не удалять
- Домен естественным образом имеет графообразную структуру с сложными отношениями между сущностями
- Вы хотите иметь самодокументируемый API, который клиенты могут исследовать через интроспекцию
REST остаётся лучшим выбором, когда:
- Вы создаёте простые конечные точки CRUD с предсказуемыми, плоскими нагрузками
- Кэширование HTTP критично — запросы GraphQL POST не кэшируются по умолчанию
- Команда мала и поверхность API стабильна — избыточность REST ниже
- Вы сталкиваетесь с проблемой N+1 и не хотите настраивать DataLoader для её решения
Проблема N+1 стоит на паузе. В резолвере GraphQL, который загружает posts и каждый пост с author, неправильная реализация запускает один запрос к базе данных на каждый пост для загрузки автора. При 50 постах это 51 запрос на один запрос. REST не имеет этой проблемы, потому что вы контролируете именно то, что SQL выполняется на конечной точке. В GraphQL вы решаете это с помощью группировки (DataLoader или аналога) — это решаемо, но это ещё одно дополнительное дело, которое нужно правильно настроить.
Форматирование и проверка SDL
Файлы схемы накапливают неравномерное отступление, несоответствующее расстояние и структурное отклонение по мере роста команды. Перед тем как вносить изменения в схему, пройдите их через форматировщик, чтобы нормализовать отступы, отсортировать поля и обнаружить синтаксические ошибки до того, как они попадут в CI.
The Форматировщик схемы GraphQL on iotools.cloud делает это ровно — вставьте свой SDL, получите чистый, единообразно отформатированный вывод с ошибками валидации, выявленными в линиях. Оно обрабатывает схемы с несколькими типами, пользовательские скаляры, директивы и крайние случаи, которые мешают ручному форматированию. Полезно как последняя проверка перед коммитом или передачей схемы другой команде.
Вам также может понравиться
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент was added on Июн 26, 2026
