В мире GraphQL мутации — это, по сути, POST-запросы, которые позволяют изменять состояние данных на сервере. Если запросы (queries) используются для чтения данных, то мутации (mutations) — это "волшебная палочка" для добавления, обновления и удаления данных.
Мутации нужны для того, чтобы:
- Создавать новые записи в базе данных (например, новый пост или пользователя).
- Изменять существующие данные (редактировать профиль пользователя).
- Удалять записи из базы (долой скучные записи!).
Основная структура мутации в GraphQL
Прежде чем мы начнем писать код, давайте разберемся с общей структурой мутаций в GraphQL. Мутация в GraphQL — это, по сути, тип, который описывает действие. Например, если вы хотите создать пользователя, структура может выглядеть так:
mutation {
createUser(username: "admin", email: "admin@example.com") {
user {
id
username
email
}
}
}
Здесь:
createUser— это имя мутации.usernameиemail— это входные параметры для создания пользователя.- В блоке
userуказаны те данные, которые мы хотим получить в ответ.
Создание первой кастомной мутации
Давайте сделаем что-то полезное, например, добавим функциональность для создания нового объекта "Задача" (Task) в нашем проекте. Каждая задача будет иметь название, описание и статус выполнения.
Шаг 1: подготовка модели в Django
Сначала создадим модель для наших задач:
# models.py
from django.db import models
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
is_completed = models.BooleanField(default=False)
def __str__(self):
return self.title
Не забудьте применить миграции, чтобы модель появилась в базе данных:
python manage.py makemigrations
python manage.py migrate
Шаг 2: создание схемы для мутации
Теперь мы добавим нашу кастомную мутацию. Мутация будет принимать title, description и is_completed в качестве входных данных и создавать новую задачу.
# schema.py
import graphene
from graphene_django.types import DjangoObjectType
from .models import Task
# Определяем тип Task для GraphQL
class TaskType(DjangoObjectType):
class Meta:
model = Task
# Создаем мутацию для добавления задачи
class CreateTask(graphene.Mutation):
class Arguments:
# Входные аргументы для мутации
title = graphene.String(required=True)
description = graphene.String(required=True)
is_completed = graphene.Boolean()
# Выходной тип (что мы вернем пользователю)
task = graphene.Field(TaskType)
def mutate(self, info, title, description, is_completed=False):
# Создаем задачу в базе данных
task = Task.objects.create(
title=title,
description=description,
is_completed=is_completed
)
return CreateTask(task=task)
Шаг 3: регистрация мутации в схемах
Добавьте мутацию в корневую схему:
# schema.py
class Mutation(graphene.ObjectType):
create_task = CreateTask.Field()
schema = graphene.Schema(mutation=Mutation)
Шаг 4: тестирование мутации
Запустите сервер и протестируйте мутацию в GraphiQL или любом GraphQL-клиенте:
mutation {
createTask(title: "Купить молоко", description: "Сходить в магазин и купить молоко.") {
task {
id
title
description
isCompleted
}
}
}
Если вы всё сделали правильно, на выходе вы получите что-то вроде этого:
{
"data": {
"createTask": {
"task": {
"id": "1",
"title": "Купить молоко",
"description": "Сходить в магазин и купить молоко.",
"isCompleted": false
}
}
}
}
Поздравляю! Вы только что создали первую кастомную мутацию.
Добавление валидации данных
А теперь, чтобы код выглядел более профессионально, добавим валидацию. Например, убедимся, что поле title не пустое.
Изменим метод mutate следующим образом:
def mutate(self, info, title, description, is_completed=False):
if not title.strip():
raise Exception("Заголовок задачи не может быть пустым!")
task = Task.objects.create(
title=title,
description=description,
is_completed=is_completed
)
return CreateTask(task=task)
Попробуйте снова отправить запрос с пустым заголовком, и вы получите ошибку:
{
"errors": [
{
"message": "Заголовок задачи не может быть пустым!",
"locations": [{ "line": 2, "column": 3 }],
"path": ["createTask"]
}
]
}
Создание сложной мутации
Теперь усложним задачу. Допустим, мы хотим, чтобы одновременно создавалась задача и назначенный к ней пользователь. Для этого мутация принимает два набора данных: данные задачи и идентификатор пользователя.
Шаг 1: Добавление связи с пользователем
Обновите модель Task, чтобы добавить связь с пользователем:
# models.py
from django.contrib.auth.models import User
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
is_completed = models.BooleanField(default=False)
assigned_to = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
def __str__(self):
return self.title
Сделайте миграции:
python manage.py makemigrations
python manage.py migrate
Шаг 2: добавление сложной мутации
Теперь добавим функциональность для создания задачи и назначения пользователя:
class CreateComplexTask(graphene.Mutation):
class Arguments:
title = graphene.String(required=True)
description = graphene.String(required=True)
is_completed = graphene.Boolean()
user_id = graphene.Int(required=True)
task = graphene.Field(TaskType)
def mutate(self, info, title, description, is_completed, user_id):
user = User.objects.get(id=user_id)
if not user:
raise Exception("Пользователь с указанным ID не найден.")
task = Task.objects.create(
title=title,
description=description,
is_completed=is_completed,
assigned_to=user
)
return CreateComplexTask(task=task)
И зарегистрируйте эту мутацию в схеме:
class Mutation(graphene.ObjectType):
create_task = CreateTask.Field()
create_complex_task = CreateComplexTask.Field()
schema = graphene.Schema(mutation=Mutation)
Шаг 3: тестирование сложной мутации
Попробуйте выполнить эту мутацию:
mutation {
createComplexTask(
title: "Подготовить отчет",
description: "Сделать отчет к завтрашнему собранию.",
userId: 1
) {
task {
id
title
description
isCompleted
assignedTo {
username
}
}
}
}
Теперь вы сделали не просто задачу, а уже целую функциональность — связали задачу с пользователем. Такой подход часто используется в реальных приложениях.
Полезные советы и типичные ошибки
- Не забывайте про валидацию! Даже если кажется, что клиент "должен знать", что нельзя отправлять пустые строки, всегда валидируйте входные данные.
- Работа с исключениями. Оборачивайте сложные операции в
try-except, чтобы обработать неожиданные ошибки. - Профилирование производительности. Используйте инструменты вроде GraphQL Playground или Apollo Server для анализа производительности ваших мутаций.
- Проблема N+1 запросов. Если вы работаете с несколькими связанными моделями, обязательно применяйте оптимизацию с помощью
select_related()илиprefetch_related().
Теперь у вас есть всё, чтобы создавать мощные и гибкие мутации в GraphQL.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ