小洋的前端记事本(NO.5):GraphQL 小实践

这里记录着小洋童鞋在前端道路上的所思所想所感。

生命在于折腾,不在折腾中崩溃,就在折腾中涅槃。


GraphQL| A query language for your API,诞生于移动快速迭代时代,理念是给客户端提供类似”数据库操作“的能力,使其能够从服务接口这个大的“大数据库”中去筛选或修改想要的数据,用以满足前端数据的个性化需求,既保证了多样性,又控制了接口数量,期望更好的分类维护接口。GraphQL 服务器有很多的实现,Node.js 当然也不例外,下面我们就一起折腾折腾 GraphQL 吧 : )

GraphQL Node.js Server

准备

首先给大家简单的介绍一下 GraphQL 特性,GraphQL 其实是一种和后端交换数据的协议,像通过 SQL 语言可以操作数据库一样,GraphQL 通过 Schema 的方式来控制数据,并约定提供以下能力:

  • query 查询数据 详见
  • mutation 特殊查询,可以看做为修改数据 详见
  • introspection 检查数据的支持情况(下文简称”自省”) 详见

可以说很多数据交换的情景都可以使用 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

g1.png

我们可以按照上述描述数据筛选数据然后获取数据的顺序生成测试代码感受一下 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 是数组所以结果对应也是数组:

g3.png

注:通过控制台,Postman等均可构造 POST 请求验证结果,本文所有请求都通过 curl 来构造(包括添加 ContentType 请求头及 data)。

introspection

在介绍自省之前,我们可以先了解一下域,每个 GraphQL 根域都有 __schema 域,这个域有一个子域叫 queryType。我们可以通过查询这些域来了解 GraphQL 服务器支持那些查询,按照 Object 的方式理解其实就是通过对象的属性可以了解其结构及内容

g4.png

看到上面的结果大家应该都有感觉了,其实 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 会返回一个对象数组,那我想知道他包含的对象是什么结构咋变咧?

g5.png

没错我们可以通过限定 __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: ...
});

g6.png

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];
                }
            }
        }
    })
});

g7.png

调试

GraphiQL 可以快速联想搜索接口,并提供了非常强大的自省能力,是很好的提高 GraphQL 开发效率的工具

g2.png

总结

正如官方所说 GraphQL 是一个查询语言,而且目前还未完成,未来也可能会有更多更大的变动,但已经拓宽了我们的思路,让我们看到了更多的可能性。目前 GraphQL 利好主要是在于前端的开发效率,落地时需要服务端的全力配合,也存在着一定的安全风险(暴力破解接口能力等),最大的性能瓶颈可能来自于数据库查询,但究竟好不好用,关键是看你怎么用了,对吧? 我们仍在前行,阳光总在风雨后。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值