这里记录着小洋童鞋在前端道路上的所思所想所感。
生命在于折腾,不在折腾中崩溃,就在折腾中涅槃。
GraphQL| A query language for your API,诞生于移动快速迭代时代,理念是给客户端提供类似”数据库操作“的能力,使其能够从服务接口这个大的“大数据库”中去筛选或修改想要的数据,用以满足前端数据的个性化需求,既保证了多样性,又控制了接口数量,期望更好的分类维护接口。GraphQL 服务器有很多的实现,Node.js 当然也不例外,下面我们就一起折腾折腾 GraphQL 吧 : )
GraphQL Node.js Server
准备
首先给大家简单的介绍一下 GraphQL 特性,GraphQL 其实是一种和后端交换数据的协议,像通过 SQL 语言可以操作数据库一样,GraphQL 通过 Schema 的方式来控制数据,并约定提供以下能力:
可以说很多数据交换的情景都可以使用 GraphQL 的概念,我们就先基于 Express 来快速打造一个 http 服务器 ( GraphQL 官方并没有说只限于 http) 用最简单的方式体验一下 GraphQL,我们的项目起始于 package.json:
{
"name": "graphql-start",
"description": "简单体验一下 GraphQL ",
"private": true,
"dependencies": {
"babel-register": "^6.18.0", // ES6 你懂的
"babel-preset-es2015": "^6.18.0", // ES6 你懂的
"express": "^4.14.0", // 快速搭一个服务器
"body-parser": "^1.15.2", // Express 中间件,获取 GraphQL 请求用的
"graphql": "^0.8.2" // 我们的主角,处理 GraphQL 请求用的
}
}
$npm install 以后我们就可以愉快的玩耍了,首先搭好程序入口,本地服务以及处理 GraphQL 请求的 handler:
/ 入口:index.js /
require('babel-register')({
presets: [ 'es2015' ]
});
require('./server.js');
/ 本地服务:server.js /
// base
import express from 'express';
import {graphql} from 'graphql';
import bodyParser from 'body-parser';
// 组织服务端识别的 schema
import schema from './schema';
let app = express();
let PORT = 3000;
// 用 text 的方式解析 request doby,形象起见我们设置一个特殊的 Content-Type
app.use(bodyParser.text({type: 'application/graphql'}));
// 创建 "/graphql" 的路由(可以看做一个API)接收 GraphQL 请求,使用 GET ? 特殊字符,url链接长度不考虑啦?
app.post('/graphql', (req, res) => {
// 组织服务端识别的 schema, 并处理 GraphQL 请求
graphql(schema, req.body).then((result) => {
res.send(JSON.stringify(result, null, 2));
})
});
let server = app.listen(PORT, function () {
let host = server.address().address;
let port = server.address().port;
console.log('GraphQL listening at http://127.0.0.1', host, port);
});
/ schema 实例:schema.js /
import {GraphQLSchema, GraphQLInt, GraphQLString, GraphQLBoolean, GraphQLObjectType, GraphQLList} from 'graphql';
// 创建一个GraphQLSchema实例,它提供了一个配置,下面主要来写我们的顶级键 query 和 mutation
let schema = new GraphQLSchema({
query: ... ,
mutation: ...
});
export default schema;
上述代码创建了入口 index.js 、本地服务 server.js 以及 schema 实例 schema.js ,至此我们的 GraphQL 服务的骨架就出来了,下面我们只要来把 schema.js 填充完整就可以体验 GraphQL 啦~
GraphQL Schema
以下所有代码均在 schema.js 中,开始前可以先了解一下 graphql-js
我们可以按照上述描述数据
,筛选数据
然后获取数据
的顺序生成测试代码感受一下 GraphQL 的魅力吧~
目标数据
首先我们先创建一个模拟数据源 todo list:
let TODOs = [
{
"id": 111,
"title": "Read emails",
"completed": false
},
{
"id": 222,
"title": "Buy orange",
"completed": true
}
];
todo list 是一个元素为 todo object 的数组,由于我们要给客户端提供筛选及自省的能力,所以我们要通过 GraphQL 的 type 来描述所有组织结构,首先我们来描述元素 todo object
// 申明 todo object 的结构及组成
let TodoType = new GraphQLObjectType({
name: 'todo',
fields: function () {
return {
id: {
type: GraphQLInt, // Int 型
description: "todo object's id"
},
title: {
type: GraphQLString, // String 型
description: "todo object's title"
},
completed: {
type: GraphQLBoolean, // Boolean 值
description: "todo object's status"
}
}
}
});
那么 todo list 显而易见就是 new GraphQLList(TodoType)
啦,即元素为 TodoType 的数组。
Schema
有了数据源我们就可以愉快的通过 GraphQL Schema 来取数据啦,我们的第一个 Schema 就先为获取整个 todo list 吧,先在服务器上注册一个处理 GraphQL 请求的方法:
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'getTODOs', // query name
description: 'The TODOs!',
fields: function () {
return {
TODOs: { // 返回结果 {TODOs: [...]}
type: new GraphQLList(TodoType),
description: 'The TODOs List!',
resolve: function () {
// 返回 todo list
return TODOs;
}
}
}
}
})
//, mutation: ...
});
之前已经创建了一个端口为3000,路由为 /graphql 的 GraphQL API,至此终于可以 $node index.js 开始我们的 GraphQL 之旅啦~
query
比如我们可以通过下面的 query 来获取 TODOs 下的 title ,由于 TODOs 是数组所以结果对应也是数组:
注:通过控制台,Postman等均可构造 POST 请求验证结果,本文所有请求都通过 curl 来构造(包括添加 ContentType 请求头及 data)。
introspection
在介绍自省之前,我们可以先了解一下域,每个 GraphQL 根域都有 __schema 域,这个域有一个子域叫 queryType。我们可以通过查询这些域来了解 GraphQL 服务器支持那些查询,按照 Object 的方式理解其实就是通过对象的属性可以了解其结构及内容
看到上面的结果大家应该都有感觉了,其实 GraphQL 请求的成功与否与接口的 Schema 设计结构息息相关,所有的数据结构查询都是按协商好的规则进行的,还记得我们声明的 GraphQLSchema 是怎么写的吗?
{
name: 'getTODOs',
description: 'The TODOs!',
fields: function () {
return {
TODOs: {
type: new GraphQLList(TodoType),
description: 'The TODOs List!'
...
}
}
}
}
发现结果是一一对应的了吧,哈哈~ 可以记住这个“万能查询”,同时 GraphQL 也提供了__Type, __TypeKind, __Field, __InputValue等等的关键字来检测接口所支持查询的属性,比如我们现在已经知道了“/garphql”这个 API 会返回一个对象数组,那我想知道他包含的对象是什么结构咋变咧?
没错我们可以通过限定 __Type 去查已经注册的 "todo" 对象来自省接口,还有很多关键字方法都可以达到上述的效果本文就不一一列举了。细心的同学发现上面 __type(name: todo) 的用法了吧,同样也可以用到我们的代码中来查询具体某一条的 todo 对象:
let schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'getTODO',
fields: function () {
return {
todo: {
type: TodoType, // 这次我们只返回一个 todo 对象
description: 'The TODO!',
args: {
id: {
type: GraphQLInt,
required: true
}
},
resolve: function (root, param, context) {
console.log(param.id);
// 这里就可以放和数据库的交互了,本例就简单的返回第一个 todo 对象
return TODOs[0];
}
}
}
}
})
//, mutation: ...
});
mutation
有查询数据当然也有修改数据,mutation 查询和普通查询请求(query)的重要区别在于 mutation 操作是按照顺序执行的,即多次修改数据后再查询,其返回一定是最后一次修改后的结果:
let schema = new GraphQLSchema({
// query: ... ,
mutation: new GraphQLObjectType({
name: 'updateTODO',
fields: {
updateTODO: {
type: TodoType,
description: 'Update the TODO status',
args: {
id: {
type: GraphQLInt,
required: true
}
},
resolve: function (root, param, context) {
// 这里就可以放和数据库的交互了,本例就简单的更新 completed 属性
console.log(param.id);
TODOs[0].completed = true;
return TODOs[0];
}
}
}
})
});
调试
GraphiQL 可以快速联想搜索接口,并提供了非常强大的自省能力,是很好的提高 GraphQL 开发效率的工具
总结
正如官方所说 GraphQL 是一个查询语言,而且目前还未完成,未来也可能会有更多更大的变动,但已经拓宽了我们的思路,让我们看到了更多的可能性。目前 GraphQL 利好主要是在于前端的开发效率,落地时需要服务端的全力配合,也存在着一定的安全风险(暴力破解接口能力等),最大的性能瓶颈可能来自于数据库查询,但究竟好不好用,关键是看你怎么用了,对吧? 我们仍在前行,阳光总在风雨后。