Мутации: изменяем данные
Мутации — это операции записи в GraphQL: создать, обновить, удалить. Они описываются в корневом типе Mutation и почти всегда возвращают изменённый объект.
Запрос спрашивает «что есть», мутация говорит «сделай так». И сразу же возвращает результат, чтобы клиент обновил экран без второго запроса.
Всё, что меняет данные на сервере, описывают в корневом типе Mutation — он устроен так же, как Query, только его поля несут побочные эффекты. По соглашению имена мутаций — глаголы: createUser, updatePost, deleteComment.
type Mutation {
createPost(title: String!, body: String!): Post!
deletePost(id: ID!): Boolean!
}
Ключевая деталь: мутация почти всегда возвращает результат — обычно созданный или изменённый объект. Это позволяет клиенту одним запросом и изменить данные, и сразу получить актуальное состояние для обновления интерфейса. Сравни с REST, где после POST часто приходит лишь статус 201 и заголовок Location, и за свежими данными нужен отдельный GET. В GraphQL такой второй круг не нужен: ты прямо в мутации указываешь, какие поля результата хочешь увидеть, и сервер вернёт их в том же ответе. Более того, клиентские библиотеки умеют автоматически вписать возвращённый объект в свой кэш — и интерфейс обновится сам, без ручного перезапроса. Поэтому хорошая мутация — это не «выполни и забудь», а «выполни и верни ровно то, что нужно экрану».
Как выглядит вызов
mutation CreatePost($title: String!, $body: String!) {
createPost(title: $title, body: $body) {
id # сервер вернул сгенерированный id
title
createdAt
}
}
Обрати внимание: после вызова мутации в фигурных скобках мы выбираем поля результата — ровно как в запросе. То есть мутация = «выполни действие и верни мне вот эти поля результата».
Как работает под капотом
Сервер видит ключевое слово mutation, берёт корневой тип Mutation и вызывает резолвер поля. Важная гарантия спецификации: поля верхнего уровня мутации выполняются последовательно, одно за другим, в порядке их перечисления — чтобы избежать гонок при нескольких записях в одном запросе (поля же обычного запроса резолвятся параллельно). Смоделируем «хранилище» и резолверы мутаций:
// простое in-memory хранилище
const store = { posts: [], nextId: 1 };
function createPost(_parent, args) {
const post = { id: store.nextId++, title: args.title, body: args.body };
store.posts.push(post);
return post; // возвращаем созданный объект
}
function deletePost(_parent, args) {
const before = store.posts.length;
store.posts = store.posts.filter(p => p.id !== args.id);
return store.posts.length < before; // true, если удалили
}
console.log(createPost(null, { title: "Привет", body: "..." })); // {id:1,...}
console.log(createPost(null, { title: "Второй", body: "..." })); // {id:2,...}
console.log(deletePost(null, { id: 1 })); // true
console.log(store.posts); // остался id:2
Попробуй сам ▶ — вызови createPost ещё раз и проверь, как растёт nextId и наполняется store.posts. Это в точности логика серверного резолвера записи.
Частые ошибки
- Не возвращать ничего полезного. Мутация, возвращающая только
Boolean, заставляет клиента делать второй запрос за свежими данными. Возвращай изменённый объект. - Класть запись в тип Query. Любые побочные эффекты — только в Mutation. Поля Query должны быть «чистыми» (read-only).
- Рассчитывать на параллельность. Несколько мутаций верхнего уровня в одном запросе выполнятся строго по очереди — это и плюс (нет гонок), и то, о чём надо помнить.
Best practices
- Имена мутаций — глаголы в стиле «действие+сущность»:
createOrder,publishPost,archiveUser. - Возвращай изменённый объект целиком, чтобы клиентский кэш обновился без дополнительного запроса.
- Для сложных входных данных используй input-типы (следующий урок) вместо длинного списка отдельных аргументов.
Итоги
Мутации — операции записи в корневом типе Mutation: создать, обновить, удалить. Они выполняются последовательно и почти всегда возвращают изменённый объект, чтобы клиент сразу обновил состояние. Дальше структурируем входные данные мутаций через input-типы и научимся отдавать понятные ошибки.