GraphQL系列三 JavaScript实战入门

本篇主要使用express-graphql来作为框架来演示枚举(enum)、输入类型(input)、参数(argument)、接口(interface)、联合(union)等服务端以及客户端的使用。本例代码存放在 https://github.com/zhaiqianfeng/GraphQL-Demo/tree/master/javascript/advance
GraphQL

环境配置

首先需要安装express、express-graphql、graphql,命令如下

npm install express express-graphql graphql

使用GraphQL语言定义服务端

示例源码文件:advance/GraphiQL-simple.js

定义schema
type User{
  name: String!
  sex: String
  intro: String
  skills: [String]!
}
input UserInput {
  name: String!
  sex: String
  intro: String
  skills: [String]!
}
type Query {
  user(id:Int!):User
  users:[User]
}
type Mutation{
  addUser(name:String!,sex:String,intro:String,skills:[String]!):User
  addUserByInput(userInfo:UserInput!):User
}

其中定义了实体User、输入类型UserInput,带参数Query的user和不带参数的users,使用标量mutation的addUser和输入类型的addUserByInput。

示例的数据

服务端定义的示例数据如下

var users=[
{
name: 'zhaiqianfeng',
sex: '男',
intro: '博主,专注于Linux,Java,nodeJs,Web前端:Html5,JavaScript,CSS3',
skills: ['Linux','Java','nodeJs','前端'],
},
{
name: 'James',
sex: '男',
intro: 'zhaiqianfeng的英文名',
skills: ['Linux','Java','nodeJs','前端'],
},
];

resolver

相应的resolver如下

// query resolver
user: function ({id}) {
return users[id];
},
users: function () {
return users;
},
//mutation resolver
addUser:function({name,sex,intro,skills}){
var user={
name:name,
sex:sex,
intro:intro,
skills:skills
};
users.push(user);
return user;
},
addUserByInput:function({userInfo}){
var user={
name:userInfo.name,
sex:userInfo.sex,
intro:userInfo.intro,
skills:userInfo.skills
};
users.push(user);
return user;
}

客户端query示例

启动服务,使用GraphiQL客户端,query查询示例如下

{
user(id:0) {
name
sex
intro
skills
}
users {
name
sex
intro
skills
}
}

客户端mutation示例

客户端更新维护示例如下

mutation{
addUser(
name:"test1User",
sex:"男",
intro:"简介",
skills:[]
){
name
sex
intro
}
addUserByInput(
userInfo:{
name:"test2User",
sex:"男",
intro:"简介",
skills:[]
}
){
name
sex
intro
}
}

使用GraphQL.js定义服务端

示例源码文件:advance/GraphiQL-good.js

GraphQL定义弊端

使用GraphQL语言来定义服务端,虽然对于操作可以自定义reslover,但字段只能使用默认的resolver,功能受限。比如根据单位参数如米和毫米的不同,返回相应的身高。查询类似

{
user(id:0){
name
MM:stature(unit:MM)
cm:stature(unit:cm)
}
}

而且使用GraphQL语言来定义服务端,reslover和schema定义是分开的,也不够直观,可以考虑使用GraphQL.js来定义。

导入GraphQL.js及类型
var {
GraphQLList,
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLInt,
GraphQLFloat,
GraphQLEnumType,
GraphQLNonNull,
GraphQLInterfaceType,
GraphQLInputObjectType
} = require('graphql');
定义schema
//定义schema及resolver
const Unit=new GraphQLEnumType({
name:'Unit',
description:"单位",
values: {
MM: {value: 'MM'},
cm: {value: 'cm'},
mm: {value: 'mm'},
}
});
const User=new GraphQLObjectType({
name:'User',
description:"用户信息实体",
fields: () => {
return ({
name: {type: new GraphQLNonNull(GraphQLString)},
sex: {type: new GraphQLNonNull(GraphQLString)},
intro: {type: new GraphQLNonNull(GraphQLString)},
skills: {type: new GraphQLNonNull(new GraphQLList(GraphQLString))},
stature: {
type: GraphQLFloat,
args: {
unit: {type: Unit}
},
resolve: function (user, {unit}) {
if (unit == 'MM') {
return user.stature/100;
} if (unit == 'cm') {
return user.stature;
}else if (unit == 'mm') {
return user.stature*10;
}
}
},
});
},
});
const UserInput=new GraphQLInputObjectType({
name:'UserInput',
description:"用户信息Input实体",
fields:()=>({
name:{type:new GraphQLNonNull(GraphQLString)},
sex:{type:new GraphQLNonNull(GraphQLString)},
intro:{type:new GraphQLNonNull(GraphQLString)},
skills:{type:new GraphQLNonNull(new GraphQLList(GraphQLString))},
stature:{type:Unit},
}),
});

其中可以看到字段stature的resolver定义。

定义Query和Mutation
const Query=new GraphQLObjectType({
name:'UserQuery',
description:'用户信息查询',
fields:()=>({
user:{
type:User,
description:'根据id查询单个用户',
args: {
id: {type: new GraphQLNonNull(GraphQLInt)}
},
resolve:function (source,{id}) {
return users[id];
}
},
users:{
type:new GraphQLList(User),
description:'查询全部用户列表',
resolve:function () {
return users;
}
}
}),
});
const Mutation=new GraphQLObjectType({
name:'UserMutation',
description:'用户信息维护',
fields:()=>({
addUser:{
type:User,
description:'添加用户',
args: {
name:{type: new GraphQLNonNull(GraphQLString)},
sex:{type: new GraphQLNonNull(GraphQLString)},
intro:{type: new GraphQLNonNull(GraphQLString)},
skills:{type:new GraphQLList(new GraphQLNonNull(GraphQLString))}
},
resolve:function (source,{name,sex,intro,skills}) {
var user={
name:name,
sex:sex,
intro:intro,
skills:skills
};
users.push(user);
return user;
}
},
addUserByInput:{
type:User,
description:'通过Input添加用户',
args: {
userInfo:{type: UserInput},
},
resolve:function (source,{userInfo}) {
console.log(userInfo);
var user={
name:userInfo.name,
sex:userInfo.sex,
intro:userInfo.intro,
skills:userInfo.skills
};
users.push(user);
return user;
}
}
}),
});
const schema = new GraphQLSchema({
query: Query,
mutation: Mutation
});
客户端query示例
{
user(id:0){
name
MM:stature(unit:MM)
cm:stature(unit:cm)
}
}

可见身高可以根据不同的单位能够返回相应的值

{
"data": {
"user": {
"name": "zhaiqiafneng",
"MM": 1.8,
"cm": 180
}
}
}

接口(interface)示例

示例源码文件:advance/GraphiQL-interface.js

接口定义
const Animal = new GraphQLInterfaceType({
name: 'Animal',
description: '接口',
fields: () => ({
name: {type: new GraphQLNonNull(GraphQLString)},
}),
resolveType:function (obj) {
if(obj.legs) {
return Dog;
}else if(obj.tailColor){
return Fish;
}else{
return null;
}
}
});

定义具有name字段的Animal接口。

实现的定义
const Dog = new GraphQLObjectType({
name: 'Dog',
interfaces: [Animal],
description: '狗狗实体',
fields: () => ({
name: {type: new GraphQLNonNull(GraphQLString)},
legs: {type: new GraphQLNonNull(GraphQLInt)},
}),
isTypeOf:obj=>obj.legs,
});
const Fish=new GraphQLObjectType({
name:'Fish',
interfaces:[Animal],
description:"鱼儿实体",
fields: () => {
return ({
name: {type: new GraphQLNonNull(GraphQLString)},
tailColor: {type: new GraphQLNonNull(GraphQLString)},
});
},
isTypeOf:obj=>obj.tailColor,
});

上面分别定义了Animal接口的实现Dog和Fish,它们具有接口共用的name字段外,Dog具有legs、Fish具有tailColor特有字段。

这里需要注意类型转换,你可以在接口中使用resolveType来实现类型转换,你也可以在实现中使用isTypeOf来转换。

示例的数据
var animals=[
{
name: 'dog',
legs: 4
},
{
name: 'fish',
tailColor:'red'
},
];
定义query接口操作
const Query=new GraphQLObjectType({
name:'AnimalQuery',
description:'动物信息查询',
fields:()=>({
animals:{
type:new GraphQLList(Animal),
description:'查询全部动物列表',
resolve:function () {
return animals;
}
},
animalSearch:{
type:Animal,
args: {
text: {type: new GraphQLNonNull(GraphQLString)}
},
resolve:function (source,{text}) {
if (text == 'dog') {
return animals[0];
}else{
return animals[1];
}
}
}
}),
});
const schema = new GraphQLSchema({
types: [Dog, Fish,Animal],
query: Query
});

其中需要在schemal中把涉及到的类型使用types来指出。

客户端query示例
{
animals{
name
... on Dog{
legs
}
... on Fish{
tailColor
}
}
}

这里使用了内联的fragment,当然你也使用外联

{
animals{
name
... dogQuery
... fishQuery
}
}
fragment dogQuery on Dog{
legs
}
fragment fishQuery on Fish{
tailColor
}

联合(union)示例

示例源码文件:advance/GraphiQL-union.js

定义实体类型
const Dog = new GraphQLObjectType({
name: 'Dog',
description: '狗狗实体',
fields: () => ({
chinaName: {type: new GraphQLNonNull(GraphQLString)},
legs: {type: new GraphQLNonNull(GraphQLInt)},
}),
isTypeOf:obj=>obj.legs,
});
const Fish=new GraphQLObjectType({
name:'Fish',
description:"鱼儿实体",
fields: () => {
return ({
englishName: {type: new GraphQLNonNull(GraphQLString)},
tailColor: {type: new GraphQLNonNull(GraphQLString)},
});
},
isTypeOf:obj=>obj.tailColor,
});
union定义
const Animal = new GraphQLUnionType({
name: 'Animal',
description: 'Union',
types:[Dog,Fish],
resolveType:function (obj) {
if(obj.legs) {
return Dog;
}else if(obj.tailColor){
return Fish;
}else{
return null;
}
}
});

这里也需要类型转换resolveType。

示例的数据
var animals=[
{
chinaName: '狗狗',
legs: 4
},
{
englishName: 'fish',
tailColor:'red'
},
];
定义query接口操作
const Query=new GraphQLObjectType({
name:'AnimalQuery',
description:'动物信息查询',
fields:()=>({
animals:{
type:new GraphQLList(Animal),
description:'查询全部动物列表',
resolve:function () {
return animals;
}
}
}),
});
const schema = new GraphQLSchema({
types: [Dog, Fish,Animal],
query: Query
});
客户端query示例
{
animals{
... on Dog{
chinaName
legs
}
... on Fish{
englishName
tailColor
}
}
}

指令(directive)示例

示例源码文件:advance/GraphiQL-directive.js。这里借用上面接口的代码(advance/GraphiQL-interface.js)来构件爱你服务端,主要看看客户端如何查询。

通过GraphiQL查询示例

如果你看过这篇博文GraphQL系列一快速入门教程,应该比较熟悉GraphiQL,query为

query(
$noWithDog:Boolean!,
$withName:Boolean!,
$withFish:Boolean!
){
animals{
name @include(if:$withName)
... dogQuery @skip(if:$noWithDog)
... on Fish @include(if:$withFish){
tailColor
}
}
}
fragment dogQuery on Dog{
legs
}

参数窗口填入

{
"noWithDog": true,
"withName": true,
"withFish": true
}

如下图
GraphiQL

通过graphql执行示例

GraphQL的客户端不止一种,仅以graphql.js为例,graphql的原型如下

function graphql(
schema: GraphQLSchema,
requestString: string,
rootValue?: mixed,
contextValue?: mixed,
variableValues?: ?{[key: string]: mixed},
operationName?: ?string
)

因此可以这样执行并打印

//测试解析请求
graphql(schema, 'query(\
$noWithDog:Boolean!,\
$withName:Boolean!,\
$withFish:Boolean!\
){\
animals{\
name @include(if:$withName)\
... dogQuery @skip(if:$noWithDog)\
... on Fish @include(if:$withFish){\
tailColor\
}\
}\
}\
fragment dogQuery on Dog{\
legs\
}',null,null,
{
"noWithDog": true,
"withName": true,
"withFish": true
}).then((response) => {
console.log(response);
});

各种类库及工具

目前已经有各种语言类库/框架工具对GraphQL提供了支持,下面列出一些供参考
服务端

客户端

翟前锋 wechat
欢迎订阅我的微信公众号:zhaiqianfeng!