Продолжаем изучать GraphQL. Сегодня мы разберем, как создавать запросы (queries) для получения данных
и мутации (mutations) для изменения данных. Приступаем!
Основы работы с Query в GraphQL
Query — это способ запрашивать данные от API. Главная особенность GraphQL в том, что клиент сам определяет структуру ответа. Это немного похоже на поход в ресторан, где вы не заказываете "суп", а говорите: "Мне, пожалуйста, суп, но только без морковки и побольше картошки".
Начнем с простого примера запроса, например, списка пользователей. Предположим, у нас уже есть модель пользователя:
# models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
Теперь создадим GraphQL-схему для получения данных о пользователях:
# schema.py
import graphene
from graphene_django.types import DjangoObjectType
from .models import User
class UserType(DjangoObjectType):
class Meta:
model = User
class Query(graphene.ObjectType):
all_users = graphene.List(UserType)
def resolve_all_users(root, info):
return User.objects.all()
schema = graphene.Schema(query=Query)
DjangoObjectType— это специальный тип, который позволяет автоматически связать нашу модель Django с GraphQL.all_users— описание запроса: он вернет список всех пользователей.resolve_all_users— функция, которая выполняет запрос к базе данных и возвращает данные.
Теперь мы можем протестировать это в GraphiQL или другом инструменте:
query {
allUsers {
name
email
}
}
В ответе мы получим:
{
"data": {
"allUsers": [
{
"name": "Alice",
"email": "alice@example.com"
},
{
"name": "Bob",
"email": "bob@example.com"
}
]
}
}
Динамические запросы: работа с параметрами
Иногда нам нужны данные не обо всех пользователях, а о конкретном. Добавим возможность искать пользователя по ID:
class Query(graphene.ObjectType):
user_by_id = graphene.Field(UserType, id=graphene.Int(required=True))
def resolve_user_by_id(root, info, id):
return User.objects.get(pk=id)
Теперь запрос может выглядеть так:
query {
userById(id: 1) {
name
email
}
}
А ответ:
{
"data": {
"userById": {
"name": "Alice",
"email": "alice@example.com"
}
}
}
обращение к несуществующему ID приведет к исключению DoesNotExist. Этот сценарий можно обработать, возвращая None или выбрасывая понятное клиенту исключение.
Основы работы с Mutation в GraphQL
Mutation (мутация) — это способ изменять данные (добавлять, обновлять или удалять). Концептуально это похоже на HTTP POST/PUT/DELETE запросы в REST, но работает гибче и централизованно.
Приведём пример базовой мутации. Добавим возможность создавать пользователя через GraphQL:
class CreateUser(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
email = graphene.String(required=True)
user = graphene.Field(UserType)
def mutate(root, info, name, email):
user = User(name=name, email=email)
user.save()
return CreateUser(user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
Как это работает:
Arguments— описывает входные параметры для мутации.mutate— функция, выполняющая логику создания. Она получает параметры, создает объект и возвращает его.user— результат, который вернется клиенту.
Теперь тестируем мутацию:
mutation {
createUser(name: "Charlie", email: "charlie@example.com") {
user {
name
email
}
}
}
В ответе мы получаем:
{
"data": {
"createUser": {
"user": {
"name": "Charlie",
"email": "charlie@example.com"
}
}
}
}
И запись появляется в базе данных. Успех!
Обновление данных с помощью Mutation
Окей, пользователь создал свой аккаунт с опечаткой в имени. Давайте добавим функцию для обновления имени пользователя:
class UpdateUser(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
name = graphene.String(required=True)
user = graphene.Field(UserType)
def mutate(root, info, id, name):
user = User.objects.get(pk=id)
user.name = name
user.save()
return UpdateUser(user=user)
class Mutation(graphene.ObjectType):
update_user = UpdateUser.Field()
Запрос для обновления:
mutation {
updateUser(id: 1, name: "Alice Updated") {
user {
name
}
}
}
И в ответе видим:
{
"data": {
"updateUser": {
"user": {
"name": "Alice Updated"
}
}
}
}
запрос к несуществующему ID также может выбросить исключение, поэтому добавление проверки с сообщением об ошибке — хорошая практика.
Удаление данных с помощью Mutation
Добавим функцию удаления пользователя:
class DeleteUser(graphene.Mutation):
class Arguments:
id = graphene.Int(required=True)
ok = graphene.Boolean()
def mutate(root, info, id):
try:
user = User.objects.get(pk=id)
user.delete()
return DeleteUser(ok=True)
except User.DoesNotExist:
return DeleteUser(ok=False)
class Mutation(graphene.ObjectType):
delete_user = DeleteUser.Field()
Теперь запрос для удаления:
mutation {
deleteUser(id: 1) {
ok
}
}
Ответ, если ID существует:
{
"data": {
"deleteUser": {
"ok": true
}
}
}
Тестирование запросов и мутаций
Не забывайте тестировать ваши запросы и мутации через GraphiQL или GraphQL Playground. Убедитесь, что запросы ведут себя предсказуемо, а ошибки возвращаются в понятном формате клиенту.
Пример ошибок:
- Отсутствие обязательного параметра: GraphQL возвращает ошибку о недостающем аргументе.
- Некорректный параметр: стоит убедиться, что введенные значения валидируются, например, email проверяется на корректность.
Оптимизация запросов
Для повышения скорости, особенно при работе с большими объемами данных, используйте Django QuerySet методы select_related и prefetch_related, чтобы минимизировать количество запросов к базе данных.
Пример с select_related:
def resolve_all_users(root, info):
return User.objects.select_related("profile").all()
GraphQL дает возможность объединить запросы и мутации в единый, мощный инструмент взаимодействия между клиентом и сервером. При правильной настройке вы сможете достичь гибкости и эффективности, о которой REST только мечтает. Вперед к следующей лекции! 😉
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ