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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ