В прошлом уроке мы назвали схему контрактом между фронтендом и бэкендом. Теперь научимся её писать. Представьте анкету при записи в клинику: в ней перечислены все поля (имя, дата рождения, телефон), у каждого поля понятный формат ответа, а обязательные помечены красной звёздочкой. Схема GraphQL — такая же анкета, только для данных вашего API: она заранее описывает, какие бывают типы данных, из каких полей они состоят и какие поля обязательны. Сервер не вернёт и не примет ничего, чего нет в схеме. В этом уроке мы опишем схему блога — с пользователями, постами и комментариями — и будем использовать её на протяжении всего модуля.
SDL: язык, на котором пишут схему
Схему описывают на SDL (Schema Definition Language, «язык описания схемы»). Это не JavaScript и не JSON, а маленький декларативный язык: вы не пишете, как получить данные, а лишь описываете, какими они будут. Главный «кирпичик» — объявление типа:
type Post {
id: ID!
title: String!
body: String!
published: Boolean!
}Разберём строку title: String!:
title— имя поля;String— тип значения (текст);!— значение никогда не бываетnull; об этом знаке поговорим подробно чуть ниже.
Есть договорённости об именах, которые соблюдают почти все: типы пишут в PascalCase (Post, CreatePostInput), поля и аргументы — в camelCase (createdAt), значения enum — в SCREAMING_SNAKE_CASE (NEWEST). Комментарии начинаются с #, сервер их игнорирует. Порядок объявления типов не важен: тип можно использовать раньше, чем он описан.
Скалярные типы: «атомы» данных
В конце любого запроса должны остаться конкретные значения: число, текст, «да/нет». Такие «листья» называются скалярами. В GraphQL есть пять встроенных:
| Тип | Что хранит | Пример |
|---|---|---|
Int | целое число (32 бита) | 42 |
Float | число с дробной частью | 19.9 (цена в AZN) |
String | текст в UTF-8 | "Hello" |
Boolean | логическое значение | true |
ID | уникальный идентификатор | "42" |
Три нюанса, на которых спотыкаются новички:
IDв ответе — всегда строка. Даже если в базе это число42, клиент получит"42". Идентификатор нужен, чтобы находить и сравнивать записи, а не считать: складывать дваIDбессмысленно.Int— 32-битное. Максимум — 2 147 483 647 (около 2,1 млрд). Для чисел побольше (например, идентификаторов из соцсетей) берутString.- Дат в списке нет. Встроенного типа для даты и времени нет. Обычно дату передают строкой ISO 8601, например
"2026-03-15T10:30:00Z", — так мы и поступим с полемcreatedAt. В больших проектах объявляют свой скаляр (scalar DateTime), например с помощью библиотеки graphql-scalars.
Объектные типы и связи между ними
Главный вид типов — объектный: именованный набор полей. Опишем блог. В нём три сущности: пользователь (User), пост (Post) и комментарий (Comment):
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
body: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: String!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
}