JavaRush /Курсы /Модуль 3: Django /Создание кастомных мутаций в GraphQL

Создание кастомных мутаций в GraphQL

Модуль 3: Django
25 уровень , 8 лекция
Открыта

В мире GraphQL мутации — это, по сути, POST-запросы, которые позволяют изменять состояние данных на сервере. Если запросы (queries) используются для чтения данных, то мутации (mutations) — это "волшебная палочка" для добавления, обновления и удаления данных.

Мутации нужны для того, чтобы:

  1. Создавать новые записи в базе данных (например, новый пост или пользователя).
  2. Изменять существующие данные (редактировать профиль пользователя).
  3. Удалять записи из базы (долой скучные записи!).

Основная структура мутации в 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
      }
    }
  }
}

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

Полезные советы и типичные ошибки

  1. Не забывайте про валидацию! Даже если кажется, что клиент "должен знать", что нельзя отправлять пустые строки, всегда валидируйте входные данные.
  2. Работа с исключениями. Оборачивайте сложные операции в try-except, чтобы обработать неожиданные ошибки.
  3. Профилирование производительности. Используйте инструменты вроде GraphQL Playground или Apollo Server для анализа производительности ваших мутаций.
  4. Проблема N+1 запросов. Если вы работаете с несколькими связанными моделями, обязательно применяйте оптимизацию с помощью select_related() или prefetch_related().

Теперь у вас есть всё, чтобы создавать мощные и гибкие мутации в GraphQL.

1
Задача
Модуль 3: Django, 25 уровень, 8 лекция
Недоступна
Разработка мутации для обновления данных
Разработка мутации для обновления данных
1
Задача
Модуль 3: Django, 25 уровень, 8 лекция
Недоступна
Создание сложной мутации с валидацией
Создание сложной мутации с валидацией
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ