Перейти к содержимому

Основные концепции

GraphQL — язык запросов для API (разработан Facebook, открыт в 2015). Клиент сам выбирает нужные поля (нет over-fetching/under-fetching), ответ в той же структуре, один эндпоинт (/graphql). Основные компоненты: типы (Object, Scalar: Int/Float/String/Boolean/ID, Enum, Input), схема (контракт), Query (чтение), Mutation (изменение), Subscription (реальное время, WebSocket). Фрагменты (переиспользование полей), переменные (отделение значений), директивы (@include, @skip). Интроспекция — API самоописывает себя (автодокументация). Резолверы — функции на сервере для каждого поля (parent, args, context, info). Отличие от REST: REST — много эндпоинтов, сервер решает что отдать; GraphQL — один эндпоинт, клиент решает. Преимущества: нет избыточности/недостаточности, один запрос вместо многих, строгая типизация. Недостатки: сложность на сервере (N+1 проблема, DataLoader), кеширование (HTTP кеш не работает, POST запросы), загрузка файлов. Типичные ошибки: игнорирование N+1, слишком глубокие запросы (лимит глубины).

Введение: Клиент заказывает музыку

Представьте, что вы зашли в ресторан. В меню десятки блюд. Вы можете заказать только то, что хотите, и ровно в том количестве, которое вам нужно. Вы не обязаны брать комплексный обед, где есть и суп, и салат, и компот. Вы говорите официанту: “Дайте мне только стейк, без гарнира”. И вам приносят ровно это.

GraphQL работает по тому же принципу. Клиент сам решает, какие данные ему нужны. Не сервер решает, что отдать, как в REST, где вы получаете весь объект целиком. Клиент пишет запрос, в котором перечисляет поля, которые хочет получить, и сервер возвращает ровно эти поля.

GraphQL — это язык запросов для API, разработанный Facebook в 2012 году и открытый в 2015 году. Он позволяет клиенту запрашивать именно те данные, которые ему нужны, и ничего лишнего.

GraphQL — это не просто “альтернатива REST”. Это принципиально другой подход к проектированию API. Вместо того чтобы создавать множество эндпоинтов (один на каждый ресурс), GraphQL предоставляет один эндпоинт (обычно /graphql) и мощный язык запросов, который позволяет клиенту точно описать, какие данные нужны.

Три главных принципа GraphQL

GraphQL основан на трёх ключевых идеях:

ПринципСуть
Запрашивай ровно то, что нужноКлиент сам выбирает поля. Ничего лишнего.
Получай данные в привычной структуреОтвет имеет ту же структуру, что и запрос.
Один эндпоинтВсе запросы идут на один URL (обычно /graphql).

1. Типы (Types)

В GraphQL всё строится вокруг типов. Тип описывает, какие поля есть у объекта и какого они типа.

Object Type (Объектный тип)

Самый распространённый тип. Описывает объект и его поля.

type User {
    id: ID!
    name: String!
    email: String!
    age: Int
    posts: [Post!]!
}
  • ID — уникальный идентификатор
  • String, Int, Float, Boolean — скалярные типы
  • ! означает “не может быть null” (обязательное поле)
  • [Post!]! — список объектов Post, которые не могут быть null

Scalar Types (Скалярные типы)

Базовые типы, которые не имеют вложенных полей.

ТипОписаниеПример
IntЦелое число42, -10
FloatЧисло с плавающей точкой3.14, -0.5
StringСтрока UTF-8"Hello", "Иван"
BooleanИстина или ложьtrue, false
IDУникальный идентификатор"123", "user-42"

Custom Scalars (Пользовательские скалярные типы)

Можно создавать свои скалярные типы.

scalar Date
scalar JSON
scalar Email

type User {
    id: ID!
    createdAt: Date!
    metadata: JSON
    email: Email!
}

Enum Types (Перечисления)

Ограниченный набор значений.

enum UserRole {
    ADMIN
    MANAGER
    USER
    GUEST
}

type User {
    id: ID!
    role: UserRole!
}

Input Types (Входные типы)

Используются для передачи сложных объектов в мутациях.

input CreateUserInput {
    name: String!
    email: String!
    age: Int
    role: UserRole
}

type Mutation {
    createUser(input: CreateUserInput!): User!
}

2. Схема (Schema)

Схема — это определение всех типов, которые доступны в API. Это контракт между клиентом и сервером.

# Определения типов
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
}

type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
}

# Корневые типы (точки входа)
type Query {
    user(id: ID!): User
    users(limit: Int, offset: Int): [User!]!
    post(id: ID!): Post
}

type Mutation {
    createUser(name: String!, email: String!): User!
    updateUser(id: ID!, name: String): User!
    deleteUser(id: ID!): Boolean!
}

type Subscription {
    userCreated: User!
    userUpdated: User!
}

Корневые типы

ТипНазначение
QueryЧтение данных (аналог GET в REST)
MutationИзменение данных (аналог POST, PUT, DELETE)
SubscriptionРеальное время (WebSocket, события)

3. Запросы (Queries)

Запросы используются для чтения данных.

Простой запрос

query {
    user(id: "123") {
        name
        email
    }
}

Ответ:

{
    "data": {
        "user": {
            "name": "Иван Петров",
            "email": "ivan@example.com"
        }
    }
}

Вложенные запросы

query {
    user(id: "123") {
        name
        email
        posts {
            title
            createdAt
        }
    }
}

Ответ:

{
    "data": {
        "user": {
            "name": "Иван Петров",
            "email": "ivan@example.com",
            "posts": [
                {"title": "Мой первый пост", "createdAt": "2024-01-15"},
                {"title": "GraphQL это круто", "createdAt": "2024-02-20"}
            ]
        }
    }
}

Запрос с параметрами

query {
    users(limit: 10, offset: 20) {
        id
        name
    }
}

Псевдонимы (Aliases)

Позволяют запросить одно и то же поле с разными параметрами.

query {
    user1: user(id: "123") {
        name
    }
    user2: user(id: "456") {
        name
    }
}

Ответ:

{
    "data": {
        "user1": {"name": "Иван"},
        "user2": {"name": "Петр"}
    }
}

Фрагменты (Fragments)

Позволяют переиспользовать набор полей.

fragment UserFields on User {
    id
    name
    email
    createdAt
}

query {
    user(id: "123") {
        ...UserFields
    }
    users(limit: 10) {
        ...UserFields
    }
}

Переменные (Variables)

Позволяют отделить значения от структуры запроса.

# Запрос
query GetUser($userId: ID!) {
    user(id: $userId) {
        name
        email
    }
}
# Переменные
{
    "userId": "123"
}

Директивы (Directives)

Позволяют включать или исключать поля по условию.

ДирективаНазначение
@include(if: Boolean)Включить поле, если условие истинно
@skip(if: Boolean)Пропустить поле, если условие истинно
query GetUser($showEmail: Boolean!) {
    user(id: "123") {
        name
        email @include(if: $showEmail)
    }
}

4. Мутации (Mutations)

Мутации используются для изменения данных.

Создание (Create)

mutation {
    createUser(name: "Иван", email: "ivan@example.com") {
        id
        name
        email
        createdAt
    }
}

Обновление (Update)

mutation {
    updateUser(id: "123", name: "Иван Петров") {
        id
        name
        email
    }
}

Удаление (Delete)

mutation {
    deleteUser(id: "123")
}

Мутация с переменными

mutation CreateUser($input: CreateUserInput!) {
    createUser(input: $input) {
        id
        name
    }
}
{
    "input": {
        "name": "Иван",
        "email": "ivan@example.com",
        "age": 30
    }
}

Множественные мутации

mutation {
    user1: createUser(name: "Иван") {
        id
    }
    user2: createUser(name: "Петр") {
        id
    }
}

5. Подписки (Subscriptions)

Подписки позволяют получать данные в реальном времени через WebSocket.

subscription {
    userCreated {
        id
        name
        email
    }
}

Как это работает:

  1. Клиент открывает WebSocket соединение
  2. Отправляет subscription запрос
  3. Сервер отправляет данные каждый раз, когда происходит событие

6. Интроспекция (Introspection)

GraphQL API сам себя описывает. Клиент может запросить схему API через интроспекцию.

Запрос типов

query {
    __schema {
        types {
            name
            description
        }
    }
}

Запрос конкретного типа

query {
    __type(name: "User") {
        name
        fields {
            name
            type {
                name
            }
        }
    }
}

Что даёт интроспекция:

  • Автодокументация
  • Автоматическая генерация клиентского кода
  • GraphQL Playground (интерактивная документация)

7. Resolvers (Резолверы)

На сервере каждый тип и поле имеет резолвер — функцию, которая возвращает данные.

Пример резолвера (JavaScript)

const resolvers = {
    Query: {
        user: (parent, args, context, info) => {
            return db.users.findById(args.id);
        },
        users: (parent, args) => {
            return db.users.findAll({ limit: args.limit, offset: args.offset });
        }
    },
    
    Mutation: {
        createUser: (parent, args) => {
            return db.users.create(args);
        }
    },
    
    User: {
        posts: (parent, args, context) => {
            // parent — это объект пользователя
            return db.posts.findByAuthorId(parent.id);
        }
    }
};

Аргументы резолвера

АргументНазначение
parentРезультат родительского резолвера
argsАргументы, переданные в поле
contextКонтекст (аутентификация, база данных)
infoИнформация о запросе

Пример полного API

Схема

type User {
    id: ID!
    name: String!
    email: String!
    age: Int
    posts: [Post!]!
}

type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
    createdAt: DateTime!
}

type Query {
    user(id: ID!): User
    users(limit: Int = 10, offset: Int = 0): [User!]!
    post(id: ID!): Post
}

type Mutation {
    createUser(name: String!, email: String!, age: Int): User!
    updateUser(id: ID!, name: String, email: String, age: Int): User!
    deleteUser(id: ID!): Boolean!
    
    createPost(title: String!, content: String!, authorId: ID!): Post!
}

type Subscription {
    userCreated: User!
    postCreated(postId: ID): Post!
}

Пример запроса

query GetUserWithPosts($userId: ID!, $includeEmail: Boolean!) {
    user(id: $userId) {
        id
        name
        email @include(if: $includeEmail)
        posts(limit: 5) {
            title
            createdAt
        }
    }
}

GraphQL vs REST: Ключевые отличия

АспектRESTGraphQL
ЭндпоинтыМного (один на ресурс)Один (/graphql)
Получение данныхСервер решает, что отдатьКлиент решает, что получить
ИзбыточностьЧасто (over-fetching)Нет (запрашиваешь только нужное)
НедостаточностьЧасто (under-fetching)Нет (можно запросить связанные данные)
Количество запросовМожет быть многоОбычно один
ВерсионированиеURL (v1, v2)Не нужно (добавляем поля, не удаляем)
КешированиеОтличное (HTTP кеш)Сложнее
Загрузка файловПростоСложнее
СложностьПрощеСложнее (особенно на сервере)

Преимущества GraphQL

ПреимуществоОбъяснение
Нет over-fetchingКлиент получает только нужные поля, не больше
Нет under-fetchingМожно запросить связанные данные в одном запросе
Один запросВместо нескольких REST вызовов
Строгая типизацияСхема — контракт между клиентом и сервером
АвтодокументацияИнтроспекция даёт документацию бесплатно
Эволюция APIДобавляйте поля, не удаляйте старые — версионирование не нужно
Удобство для клиентовКлиент сам решает, что ему нужно

Недостатки GraphQL

НедостатокОбъяснение
Сложность на сервереРезолверы, N+1 проблема, сложная оптимизация
КешированиеHTTP кеш не работает (один эндпоинт, POST запросы)
Загрузка файловНет встроенной поддержки (нужны расширения)
Перегруженные запросыКлиент может запросить слишком глубокую вложенность
Сложность для аналитиковНужно учить GraphQL вместо привычного SQL или REST
ИнструментыМеньше, чем для REST

Распространённые ошибки

Ошибка 1: Игнорирование N+1 проблемы

// Плохо: каждый post вызывает отдельный запрос к БД
User: {
    posts: (parent) => db.posts.findByAuthorId(parent.id)
}

Исправление: DataLoader для батчинга.

Ошибка 2: Слишком глубокие запросы

query {
    user(id: "123") {
        friends {
            friends {
                friends {
                    friends {
                        name
                    }
                }
            }
        }
    }
}

Исправление: Лимит глубины запроса (например, 5 уровней).

Ошибка 3: GraphQL как серебряная пуля

Использование GraphQL для простого CRUD API с тремя таблицами.

Исправление: Для простых API REST подходит лучше.

Ошибка 4: Игнорирование кеширования

Каждый запрос — POST, ничего не кешируется.

Исправление: Использовать клиентское кеширование (Apollo Client, Relay), persisted queries, CDN.

Ошибка 5: Слишком сложные мутации

Одна мутация делает слишком много всего.

Исправление: Разделять на несколько мутаций.

Резюме для системного аналитика

  1. GraphQL — это язык запросов для API. Клиент сам решает, какие поля ему нужны. Сервер возвращает ровно то, что попросили.

  2. Три главных принципа: запрашивай ровно то, что нужно; ответ в той же структуре, что и запрос; один эндпоинт.

  3. Схема (Schema) — контракт API. Описывает типы, запросы (Query), мутации (Mutation), подписки (Subscription).

  4. Запросы (Queries) — для чтения данных. Мутации — для изменения. Подписки — для реального времени.

  5. Фрагменты (Fragments) — переиспользование полей. Переменные — отделение значений от структуры. Директивы — условное включение полей.

  6. Интроспекция позволяет API самоописываться. Автодокументация, автогенерация клиентов.

  7. Резолверы — функции на сервере, которые возвращают данные для каждого поля.

Проверка знаний

Вопрос 1 из 4
Какой базовый принцип GraphQL является главным?
Что обычно является одной точкой входа в GraphQL API?
Что описывает схема GraphQL?
Чем query отличается от mutation в GraphQL?

Вопросы, где были ошибки