在 Django 中构建 GraphQL API(Graphene 实战)
用 Graphene-Django 给 Django 应用构建 GraphQL API:Schema 定义、查询过滤、Mutation 数据修改,告别 REST 的过度与不足获取。
直接回答:GraphQL 让客户端精确声明需要的数据,减少传输层的过度获取与不足获取,但不自动消除数据库过量查询;Graphene-Django 把它与 Django ORM 打通,Schema 定义直接映射模型,一个端点服务所有前端。
为什么是 GraphQL
REST 的痛点:移动端列表页只需要 3 个字段,接口却返回 30 个;详情页又要连调 4 个接口拼数据。GraphQL 翻转权力结构——客户端写查询描述要什么,服务端一次返回,前端迭代不再绑架后端发版。
搭建基础
pip install graphene-django
# settings.py
INSTALLED_APPS = [..., "graphene_django"]
GRAPHENE = {"SCHEMA": "blog.schema.schema"}
# urls.py
from django.urls import path
from graphene_django.views import GraphQLView
urlpatterns = [path("graphql", GraphQLView.as_view(graphiql=True))]
graphiql=True 仅作为开发调试。保留 Django CSRF 中间件,Mutation 客户端须携带有效 CSRF token;生产限制 IDE 和接口访问。
定义 Schema
# schema.py
import graphene
from graphene_django import DjangoObjectType
from .models import Author, Book
class AuthorType(DjangoObjectType):
class Meta:
model = Author
fields = ("id", "name")
class BookType(DjangoObjectType):
class Meta:
model = Book
fields = ("id", "title", "price", "author")
class Query(graphene.ObjectType):
all_books = graphene.List(BookType)
book = graphene.Field(BookType, id=graphene.Int(required=True))
def resolve_all_books(root, info):
return Book.objects.select_related("author").all()[:100]
def resolve_book(root, info, id):
return Book.objects.filter(pk=id).first()
schema = graphene.Schema(query=Query)
客户端查询:
query {
allBooks {
title
author { name }
}
}
一次请求拿到书加作者——resolver 里的 select_related 顺手解决了 N+1。
查询过滤
class Query(graphene.ObjectType):
books = graphene.List(BookType, title_contains=graphene.String(), max_price=graphene.Float())
def resolve_books(root, info, title_contains=None, max_price=None):
qs = Book.objects.all()
if title_contains:
qs = qs.filter(title__icontains=title_contains)
if max_price is not None:
qs = qs.filter(price__lte=max_price)
return qs.select_related("author")[:100]
参数即过滤条件,客户端自由组合;上例 100 条限制只是演示,生产应实现有边界的分页和对象级访问控制。进阶场景可以接 django-filter 自动生成过滤集。
Mutation:修改数据
class CreateBook(graphene.Mutation):
class Arguments:
title = graphene.String(required=True)
price = graphene.Float(required=True)
author_id = graphene.Int(required=True)
book = graphene.Field(BookType)
def mutate(root, info, title, price, author_id):
from graphql import GraphQLError
from decimal import Decimal
from django.core.exceptions import ValidationError
if not info.context.user.is_authenticated or not info.context.user.is_staff:
raise GraphQLError("无创建权限")
book = Book(title=title, price=Decimal(str(price)), author_id=author_id)
try:
book.full_clean()
except ValidationError:
raise GraphQLError("图书字段无效")
book.save()
return CreateBook(book=book)
class Mutation(graphene.ObjectType):
create_book = CreateBook.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
mutation {
createBook(title: "新书", price: 59.9, authorId: 1) {
book { id title }
}
}
生产注意事项
- N+1 是 GraphQL 的头号陷阱:resolver 嵌套越深越要检查,配 dataloaders 批量加载;对同一个命名查询记录返回条数、SQL 次数和耗时,比较修改前后是否减少数据库访问。
- 查询深度与复杂度限制:防止恶意嵌套查询打爆数据库;
- 鉴权:在 resolver 或中间件里检查
info.context.user。
常见问题(FAQ)
Q:GraphQL 会取代 REST 吗?
A:不会。简单资源型 API 用 REST 更直白;前端需求多变、数据关系复杂、多端消费的场景 GraphQL 优势明显。很多团队两者并存。
Q:Graphene 和 Strawberry 选哪个?
A:Strawberry 用类型注解写 Schema,更现代;Graphene 历史久、与 Django 集成成熟。新项目两者皆可,看团队口味。
Q:文件上传怎么做?
A:GraphQL 规范之外,用 multipart 请求规范(graphene-file-upload)或干脆让上传走独立 REST 端点——务实不丢人。
官方参考
本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。