Скаляры, объекты и перечисления
Из чего собрана любая схема: скаляры (примитивы вроде Int и String), объектные типы (составные сущности) и перечисления (фиксированные наборы значений).
Скаляры — это листья дерева, объекты — его ветви. Запрос всегда обязан дойти до листьев-скаляров, иначе он недописан.
Система типов GraphQL стоит на нескольких видах именованных типов. Всего их шесть: объектные, скалярные, перечисления (enum), интерфейсы, объединения (union) и входные (input) типы. Сейчас разберём три самых базовых, с которыми ты будешь работать постоянно — на них держится 90% любой схемы, а остальные три встретятся позже в курсе по мере надобности.
Скалярные типы
Скаляры — это конкретные значения, дальше них «нырять» некуда. В спецификацию встроены пять:
| Скаляр | Что хранит |
|---|---|
Int | Целое 32-битное число |
Float | Число с плавающей точкой |
String | Строка UTF-8 |
Boolean | true или false |
ID | Уникальный идентификатор (сериализуется как строка) |
Особый случай — ID. Внешне он как строка, но семантически означает «уникальный ключ» и не предназначен для показа человеку. Можно объявлять и кастомные скаляры — например DateTime, Email, JSON — со своей логикой проверки и сериализации:
scalar DateTime
type Event {
id: ID!
title: String!
startsAt: DateTime!
}
Объектные типы
Объектный тип — это составная сущность с набором полей. Поля могут быть скалярами или ссылками на другие объектные типы — так и строится граф:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
author: Author! # ссылка на другой объектный тип
}
Перечисления (enum)
Enum ограничивает поле фиксированным набором именованных значений — отличная защита от «магических строк»:
enum Role {
ADMIN
EDITOR
VIEWER
}
type Member {
id: ID!
role: Role!
}
Клиент не сможет передать role: "boss" — валидатор отклонит всё, что не входит в перечисление.
Как работает под капотом
Для каждого скаляра у сервера есть функции сериализации (значение из БД -> в ответ) и парсинга (значение из запроса -> во внутренний вид). Для кастомного скаляра ты пишешь эти функции сам. Объектные поля, ссылающиеся на другие типы, рекурсивно резолвятся вглубь, пока обход не упрётся в скаляры-листья. Enum проверяется как принадлежность значения списку.
Смоделируем валидацию enum и приведение скаляра в JS:
const Role = ["ADMIN", "EDITOR", "VIEWER"];
function setRole(value) {
if (!Role.includes(value)) {
return "Ошибка enum: '" + value + "' не из " + Role.join("|");
}
return "ok: " + value;
}
// кастомный скаляр DateTime: парсим строку в timestamp
function parseDateTime(s) {
const t = Date.parse(s);
return isNaN(t) ? "невалидная дата" : t;
}
console.log(setRole("ADMIN")); // ok
console.log(setRole("boss")); // ошибка enum
console.log(parseDateTime("2024-05-01")); // число-таймстамп
Попробуй сам ▶ — передай в setRole значение не из списка и в parseDateTime «не дату». Так сервер защищает данные от мусора.
Частые ошибки
- Хранить идентификаторы как Int. Лучше
ID: он не привязан к числовому формату и переживёт смену схемы БД. - Запросить объектное поле без вложенных полей.
authorбез{ name }внутри — ошибка: до скаляров надо дойти. - Свободные строки вместо enum. Если у поля конечный набор значений (статусы, роли), enum избавит от опечаток и задокументирует варианты.
Best practices
- Заводи кастомные скаляры для дат, email, URL — это переносит проверку формата на уровень схемы.
- Используй enum для всего, что имеет фиксированный набор значений; добавлять новые значения в enum — безопасное изменение.
- Имена enum-значений пиши в UPPER_SNAKE_CASE — это устоявшееся соглашение.
Итоги
Типы GraphQL собираются из скаляров (Int, Float, String, Boolean, ID и кастомных), объектных типов (составные сущности со ссылками) и перечислений (фиксированные наборы значений). Запрос всегда должен дойти до скаляров-листьев. Дальше посмотрим на модификаторы ! и [], которые задают обязательность и списки.