GraphQL 是Facebook去年开源的一套数据查询语言,对于大型系统,GraphQL提供一种灵活的访问通用数据的方式。当时正好有一个项目,需要前端调用后台的数据(Postgresql)实现图表展示,我就从Postgresql里把数据导出成JSON文件,前端直接用GraphQL获取自己需要的数据,后台也省掉了制作API、写SQL的工作,运行至今基本稳定。
当时GraphQL还没有针对Django的modules,前两个月发布了 graphene-django ,做的事情不多,但可以搭配其他插件例如django-filters来实现更丰富的查询,基本能满足需求。GraphQL的优势在于查询语句的通用性和易读性,相比REST架构来说时间较短还不够成熟,如果是复杂需求后台的开发量仍然不小,现阶段在生产环境用Django做一个API服务还是rest framework等成熟的方案会更好。本文实现一个针对Django自带的User Model的简单API。
环境:
Django 1.10.4
graphene-django-1.2.0
Setup安装graphene-django,如果是新建Django项目,创建后执行migrate创建表,再创建admin用户:
pip install graphene-django
django-admin startproject dmyz
cd dmyz
python manage.py migrate
python manage.py createsuperuser
编辑 settings.py ,将graphene-django加入INSTALLED_APPS:
INSTALLED_APPS = ( # ... 'graphene_django', )再编辑 urls.py ,导入GraphQLView,加上graphql的设置:
from graphene_django.viewsimport GraphQLView urlpatterns = [ # ... url(r'^graphql', GraphQLView.as_view(graphiql=True)), ]执行runserver,这时候访问/graphql会报错,显示没有提供schema。
Schema在dmyz目录中新建 schema.py 文件,目录结构如下:
dmyz
├── db.sqlite3
├── dmyz
│ ├── __init__.py
│ ├── schema.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── manage.py
编辑 schema.py 文件,导入DjangoObjectType和graphene,处理django自带的User Model,代码如下:
from django.contrib.auth.modelsimport User as UserModel from graphene_djangoimport DjangoObjectType import graphene class User(DjangoObjectType): class Meta: model = UserModel class Query(graphene.ObjectType): users = graphene.List(User) @graphene.resolve_only_args def resolve_users(self): return UserModel.objects.all() schema = graphene.Schema(query=Query)最后编辑 settings.py ,加上配置:
GRAPHENE = { 'SCHEMA': 'dmyz.schema.schema' }现在重新访问/graphql,可以看到自带的前端界面,在左上角文本框中输入:
{ users { username email } }运行(点击上方:arrow_forward:按钮)返回admin用户的username和email。右侧的Docs会显示Query信息,下划线命名也会自动转成驼峰命名。
查询语句完整格式是 query 名称{} ,上面的查询语句省掉了 query ,因为GraphQL默认会作为 query 语句执行,如果是 mutation 就需要加上了。关于查询语句可以参考官方文档: http://graphql.org/learn/queries/

Schema & Type & Field
无论后台是Python还是Nodejs,查询语句都是一样的,但不同语言对Schema的实现方式不同。Schema定义数据的呈现结构,包含各种Types,其中Query和Mutation是两个特殊的Type,Type通过Fields指定返回的数据字段。以之前的代码为例,代码中定义了Type(Query)和Fields(users),指定resolve来处理数据。
Filter先增加一个测试用户,在后台添加或者执行:
python manage.py shell -c “from django.contrib.auth.models import User;User.objects.create_user(username=’dmyz’,email=’admin@dmyz.org’,password=’dmyz.org’)”
执行之前的查询语句会返回两条记录。修改 schema.py 的修改Query:
class Query(graphene.ObjectType): users = graphene.List(User, id=graphene.Int()) #指定id字段作为查询参数 #@graphene.resolve_only_args 注释装饰器,接收args参数 def resolve_users(self, args, context, info): if args == {}: #如果没有参数就返回所有结果 return UserModel.objects.all() return UserModel.objects.filter(pk=args.get('id')) #否则返回过滤后的结果 schema = graphene.Schema(query=Query)执行新的查询语句,指定id:
query userId{users(id: 2) {id,username,email}}新的查询语句用逗号分隔字段,增加了名字,执行后只返回id=2的用户:
curl “http://127.0.0.1:8000/graphql?query=query%20userId%7Busers(id%3A%202)%20%7Bid%2Cusername%2Cemail%7D%7D&operationName=userId” | python -m json.tool{
“data”: {
“users”: [{
“id”: “2”,
“username”: “dmyz”,
“email”: “admin@dmyz.org”
}
]
}
}
Mutation先继承graphene.Mutation创建一个类,定义输入的参数,再处理成ObjectType传递给Schema:
class CreateUser(graphene.Mutation): class Input: username = graphene.String() password = graphene.String() email = graphene.String() ok = graphene.Boolean() user = graphene.Field(lambda: User) def mutate(self, args, content, info): user = User(username=args.get('username')) ok = True return CreateUser(ok=ok, user=user) class Mutation(graphene.ObjectType): create_user = CreateUser.Field() schema = graphene.Schema(query=Query, mutation=Mutation)查询语句必须申明是 mutation ,传入Input中定义的字段,还要定义返回的内容:
mutation { createUser(username: "graphql", password: "test") { ok user { username } } }执行后返回数据:
{ "data": { "createUser": { "ok": true, "user": { "username": "graphql" } } } }