GraphQL is Facebook’s new query language for fetching application data in a uniform way.node
GraphQL并非一个面向图数据库的查询语言,而是一个数据抽象层,包括数据格式、数据关联、查询方式定义与实现等等一揽子的东西。GraphQL也并非一个具体的后端编程框架,若是将REST看作适合于简单逻辑的查询标准,那么GraphQL能够作一个独立的抽象层,经过对于多个REST风格的简单的接口的排列组合提供更多复杂多变的查询方式。与REST相比,GraphQL定义了更严格、可扩展、可维护的数据查询方式。git
GraphQL与以前Netflix出品的Falcor,都是致力于解决相同的问题:如何有效处理日益增加不断变化的Web/Mobile端复杂的数据需求。笔者一直认为,REST原论文最大的功劳在于先后端分离与无状态请求,而REST的资源化的请求方式只适合面向简单的请求,对于具备复杂资源间关联的请求就有点无能为力。关于这一点,笔者在以前的RARF系列中有过充分的讨论。github
GraphQL is a specification.shell
仍是须要强调一点,引入GraphQL并不意味着要像以前从Struts迁移到SpringBoot同样须要去修改你的真实的后端代码,所以GraphQL能够看作一个业务逻辑层灵活有效地辅助工具。这一点也是GraphQL与原来的REST API最大的差异,举例而言:数据库
{ latestPost { _id, title, content, author { name }, comments { content, author { name } } } }
这是一个很典型的GraphQL查询,在查询中指明了须要返回某个Blog的评论与做者信息,一个典型的返回结果譬如:express
{ "data": { "latestPost": { "_id": "03390abb5570ce03ae524397d215713b", "title": "New Feature: Tracking Error Status with Kadira", "content": "Here is a common feedback we received from our users ...", "author": { "name": "Pahan Sarathchandra" }, "comments": [ { "content": "This is a very good blog post", "author": { "name": "Arunoda Susiripala" } }, { "content": "Keep up the good work", "author": { "name": "Kasun Indi" } } ] } } }
而若是采用REST API方式,要么须要前端查询屡次,要么须要去添加一个新的接口,专门针对前端这种较为特殊的请求进行响应,而这样又不可避免地致使后端代码的冗余,毕竟颇有可能这个特殊的请求与返回哪天就被废了。npm
Learn GraphQLGraphQL系列教程编程
from-rest-to-graphql:从REST到GraphQL的思惟变迁json
GraphQL explained How GraphQL turns a query into a response:GraphQL简单的原理介绍,能够有助于理解GraphQL的设计理念与做用
awesome-graphql:一系列的关于GraphQL相关的资源的搜集
首先建立项目文件夹:
mkdir graphql-demo cd graphql-demo
而后使用npm安装必要的依赖:
npm init -f npm install graphql express express-graphql --save
做为一个简单的数据服务器,咱们仅使用最简单的JSON文件做为数据源:
{ "1": { "id": "1", "name": "Dan" }, "2": { "id": "2", "name": "Marie" }, "3": { "id": "3", "name": "Jessie" } }
一个简单的GraphQL服务器须要建立Scheme以及支持的查询:
// Import the required libraries var graphql = require('graphql'); var graphqlHTTP = require('express-graphql'); var express = require('express'); // Import the data you created above var data = require('./data.json'); // Define the User type with two string fields: `id` and `name`. // The type of User is GraphQLObjectType, which has child fields // with their own types (in this case, GraphQLString). var userType = new graphql.GraphQLObjectType({ name: 'User', fields: { id: { type: graphql.GraphQLString }, name: { type: graphql.GraphQLString }, } }); // Define the schema with one top-level field, `user`, that // takes an `id` argument and returns the User with that ID. // Note that the `query` is a GraphQLObjectType, just like User. // The `user` field, however, is a userType, which we defined above. var schema = new graphql.GraphQLSchema({ query: new graphql.GraphQLObjectType({ name: 'Query', fields: { user: { type: userType, // `args` describes the arguments that the `user` query accepts args: { id: { type: graphql.GraphQLString } }, // The resolve function describes how to "resolve" or fulfill // the incoming query. // In this case we use the `id` argument from above as a key // to get the User from `data` resolve: function (_, args) { return data[args.id]; } } } }) }); express() .use('/graphql', graphqlHTTP({ schema: schema, pretty: true })) .listen(3000); console.log('GraphQL server running on http://localhost:3000/graphql');
而后使用node命令启动服务器:
node index.js
若是你直接访问http://localhost:3000/graphql会获得以下反馈:
{ "errors": [ { "message": "Must provide query string." } ] }
按照以下方式能够建立一个简单的根据ID查询用户的姓名,从中能够看出基本的GraphQL的查询的样式,就是一个JSON的Key-Value对,键值就是查询值:
{ user(id: "1") { name } }
返回数据是:
{ "data": { "user": { "name": "Dan" } } }
若是你但愿以GET方式进行查询,能够移除全部的空格,即获得以下方式的请求:
http://localhost:3000/graphql?query={user(id:"1"){name}}
注意,GraphQL定义了一种通用的数据查询语言,并不必定要基于HTTP协议,不过目前绝大部分应用服务器的交互协议都是HTTP,所以这里也是基于Express以及GraphQL的JavaScript实现构建一个简单的GraphQL服务器。
$ mkdir graphql-intro && cd ./graphql-intro $ npm install express --save $ npm install babel --save $ touch ./server.js $ touch ./index.js
而核心的服务端代码为:
// index.js // by requiring `babel/register`, all of our successive `require`s will be Babel'd require('babel/register'); require('./server.js'); // server.js import express from 'express'; let app = express(); let PORT = 3000; app.post('/graphql', (req, res) => { res.send('Hello!'); }); let server = app.listen(PORT, function () { let host = server.address().address; let port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });
直接使用Node命令便可以启动服务器:
$ node index.js GraphQL listening at http://0.0.0.0:3000
能够用Curl进行简单的测试:
$ curl -XPOST http://localhost:3000/graphql Hello!
如今咱们已经建立了一个简单的HTTP Server能够进行交互,下面咱们就要为该Server添加GraphQL查询的解析的支持。首先回顾下一个基本的GraphQL的查询请求以下:
query getHighScore { score }
该查询意味着某个GraphQL的客户端但愿获取getHighScore
域的score
子域的信息,Fields就是客户端要求GraphQL返回的数听说明,一个Fields也能够包含参数,譬如:
query getHighScores(limit: 10) { score }
而咱们的GraphQL Server首先须要知道应该如何去解析这样的请求,即须要去定义Schema。构建一个Schema的过程有点相似于构建RESTful的路由树的过程,Schema会包含Server能够返回给前端的Fields以及响应中的数据类型。GraphQL中是采起了静态数据类型,所以Client能够依赖于其发起请求时声明的数据类型。首先咱们声明使用Schema所须要的依赖项:
$ npm install graphql --save $ npm install body-parser --save $ touch ./schema.js
而后咱们建立一个GraphQLSchema实例,通常来讲咱们会将配置放入一个单独的文件夹中:
// schema.js import { GraphQLObjectType, GraphQLSchema, GraphQLInt } from 'graphql/lib/type'; let count = 0; let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, resolve: function() { return count; } } } }) }); export default schema;
该Schema的定义用通俗地语言表达便是针对查询会返回一个RootQueryType的对象,而每一个RootQueryType对象会包含一个整型的count域。
在定义好了Schema以后,咱们就须要将其应用到HTTP Server中:
import express from 'express'; import schema from './schema'; // new dependencies import { graphql } from 'graphql'; import bodyParser from 'body-parser'; let app = express(); let PORT = 3000; // parse POST body as text app.use(bodyParser.text({ type: 'application/graphql' })); app.post('/graphql', (req, res) => { // execute GraphQL! graphql(schema, req.body) .then((result) => { res.send(JSON.stringify(result, null, 2)); }); }); let server = app.listen(PORT, function () { var host = server.address().address; var port = server.address().port; console.log('GraphQL listening at http://%s:%s', host, port); });
全部针对/graphql
的查询都会在定义好的Schema下执行,这里咱们默认的返回count值,仍是使用Curl进行简单的调试能够获得:
$ node ./index.js // restart your server // in another shell $ curl -XPOST -H "Content-Type:application/graphql" -d 'query RootQueryType { count }' http://localhost:3000/graphql { "data": { "count": 0 } }
实际上,GraphQL Server也能够返回其定义好的Schema信息:
$ curl -XPOST -H 'Content-Type:application/graphql' -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql { "data": { "__schema": { "queryType": { "name": "RootQueryType", "fields": [ { "name": "count", "description": null } ] } } } }
其使用的查询实际上就是这个样子:
{ __schema { queryType { name, fields { name, description } } } }
实际上,咱们也能够为每一个定义的域添加譬如description
, isDeprecated
, 以及 deprecationReason
这样的描述信息,譬如:
let schema = new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQueryType', fields: { count: { type: GraphQLInt, // add the description description: 'The count!', resolve: function() { return count; } } } }) });
那么返回的新的元信息就是:
$ curl -XPOST -H 'Content-Type:application/graphql' -d '{__schema { queryType { name, fields { name, description} }}}' http://localhost:3000/graphql { "data": { "__schema": { "queryType": { "name": "RootQueryType", "fields": [ { "name": "count", "description": "The count!" } ] } } } }
上文中所讲的都是基于GraphQL定义一个查询方式,而GraphQL也是支持对于数据的增删改,这在GraphQL中称为mutations
。Mutations也是一个域,其主要是为了指明某个请求打来的Side Effects,所以大部分的语法仍是一致的。Mutations也是须要提供一个返回值的,主要是为了返回你改变的值以供验证修改是否成功。
let schema = new GraphQLSchema({ query mutation: new GraphQLObjectType({ name: 'RootMutationType', fields: { updateCount: { type: GraphQLInt, description: 'Updates the count', resolve: function() { count += 1; return count; } } } }) });
对应的查询方式就是:
$ curl -XPOST -H 'Content-Type:application/graphql' -d 'mutation RootMutationType { updateCount }' http://localhost:3000/graphql { "data": { "updateCount": 1 } }