之前就简单接触过GraphQL,但一直没怎么上手实践。最近想用GraphQL来做后端的API层,正好可以真正地用一用了。

以下所有代码均为使用GraphQL的JavaScript的SDK。

Server side

GraphQL官方提供了一个express插件可以很方便地在express的基础上构建GraphQL API:

var express = require('express');
var graphqlHTTP = require('express-graphql');

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);

其中,graphqlHTTP接收了三个参数,graphiql: true表示会提供一个方便查询的GraphQL的前端界面,否则只有纯后端的API。另外两个参数后面会详细讲一讲。

GraphQL Schema

GraphQL自创了一套schema的语法,通过graphql.buildSchema函数可以将这套语法进行解析并生成JavaScript中对应的数据结构:

var {buildSchema} = require('graphql');

var schema = buildSchema(`
  input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

当然,我们也可以不使用这套语法,而直接使用GraphQL提供了内置类型来生成schema,下面的代码可以达到一模一样的效果:

var {GraphQLSchema, GraphQLObjectType, GraphQLInputObjectType, GraphQLString, GraphQLID} = require('graphql');

const MessageInput = new GraphQLInputObjectType({
    name: 'MessageInput',
    fields: {
        content: {type: GraphQLString},
        author: {type: GraphQLString},
    },
});

const Msg = new GraphQLObjectType({
    name: 'Msg',
    fields: {
        id: {type: GraphQLID},
        content: {type: GraphQLString},
        author: {type: GraphQLString},
    },
});

const Query = new GraphQLObjectType({
    name: 'Query',
    fields: {
        getMessage: {
            type: Msg,
            args: {
                id: {type: GraphQLID},
            },
        }
    }
});

const Mutation = new GraphQLObjectType({
    name: 'Mutation',
    fields: {
        createMessage: {
            type: Msg,
            args: {
                input: {type: MessageInput},
            },
        },
        updateMessage: {
            type: Msg,
            args: {
                id: {type: GraphQLID},
                input: {type: MessageInput},
            },
        },
    },

});

var schema = new GraphQLSchema({
    MessageInput: MessageInput,
    Message: Msg,
    query: Query,
    mutation: Mutation,
});

以上,可以看到使用GraphQL提供的语法会更加简洁一些。

此外,需要注意的是,GraphQL的schema必须包含名为query的field(用于查询用),除了query外,还有一个特殊的field叫做mutation(用于增、删、改),后面的client端的查询语法也可以看到有两个关键字与之对应。除了这些特殊的field外,其他的field是用于定义一些复杂类型的。

GraphQL rootValue

这个参数其实是和schema配合起来看的,比如上面例子中的schema中声明了三个函数getMessagecreateMessageupdateMessage,这些函数的定义就放在rootValue中:

var root = {
    getMessage: function ({id}) {
        if (!fakeDatabase[id]) {
            throw new Error('no message exists with id ' + id);
        }
        return new Message(id, fakeDatabase[id]);
    },
    createMessage: function ({input}) {
        // Create a random id for our "database".
        var id = require('crypto').randomBytes(10).toString('hex');

        fakeDatabase[id] = input;
        return new Message(id, input);
    },
    updateMessage: function ({id, input}) {
        if (!fakeDatabase[id]) {
            throw new Error('no message exists with id ' + id);
        }
        // This replaces all old data, but some apps might want partial update.
        fakeDatabase[id] = input;
        return new Message(id, input);
    },
};

GraphQL通过名称来对他们进行匹配。

除此之外,如果你的schema定义是使用的GraphQL的内置类型来创建的,那么你还可以通过每一个field的resolve属性来定义对应的函数(注意如果通过resolve定义了函数,那么rootValue中对应的函数就不会起作用了)。以getMessage函数为例:

const Query = new GraphQLObjectType({
    name: 'Query',
    fields: {
        getMessage: {
            type: Msg,
            args: {
                id: {type: GraphQLID},
            },
            resolve(args, context, info) {
                // If the resolve function is provided here, the corresponding function in `rootValue` will not be called
                const id = context.id;
                if (!fakeDatabase[id]) {
                    throw new Error('no message exists with id ' + id);
                }
                return new Message(id, fakeDatabase[id]);
            }
        }
    }
});

Client side

从客户端对GraphQL的API进行查询其实就是往GraphQL的API上发送一个POST请求,请求内容为一个Json格式的字符串,格式也同样为GraphQL所定义的一套语法,且要满足对应的schema定义。

基本格式为:

{
  "query": xxx,
  "variables": xxx
}

其中,query为满足GraphQL定义语法的查询语言,variables为query中用到的变量(如果有的话)。

对应上面server端的一个简单的例子如下:

{
  "query": "mutation CreateMessage($input: MessageInput) {\n  createMessage(input: $input) {\n    id\n  }\n}",
  "variables": {
    "input": {
      "author": "andy",
      "content": "hope is a good thing"
    }
  }
}

*上述所有完整代码可查看graphql-demo