书接上回,上一章节介绍了什么是GraphQL,本章节将介绍在Node端如何使用GraphQL。事例中使用的Node版本为17.9.0,GraphQL的版本为15.8.0,框架为Koa 版本为2.13.4。废话不多说,直接上代码
代码结构如下图
有些结构是冗余的但是并没有删除,为了方便使用,创建了controller、services、以及schema用于代码分层。分别指向入口控制层、业务处理层、以及GraphQL的schema层。
安装
npm install graphql koa koa-graphql koa-mount koa-body koa-bodyparser koa-json --save
安装完成后package.json
文件更新,在dependences中增加了相关依赖如下图
创建app.js
作为启动项目的入口
#app.js
const Koa = require("koa");
const BodyParser = require('koa-bodyparser');
const Json = require('koa-json');
const KoaBody = require('koa-body');
const Mount = require("./src/router");
const app = new Koa();
app.use(KoaBody({
multipart: true,
formidable: {
maxFileSize: 200 * 1024 * 1024 // 设置上传文件大小最大限制,默认20M
},
formLimit: "10mb",
jsonLimit: "10mb"
}));
app.use(BodyParser({
multipart: true,
enableTypes: ['json', 'form', 'text'],
formLimit: "10mb",
jsonLimit: "10mb"
}));
app.use(Json());
new Mount(app);
app.use(async function (ctx, next) {
try {
await next();
} catch (e) {
ctx.status = 404;
ctx.body = `${ctx.originalUrl} not found !`;
}
});
module.exports = app;
在src/router
文件夹下创建index.js
用于绑定GraphQL的入口
#src/router/index.js
const Router = require("koa-mount");
const {graphqlHTTP} = require("koa-graphql");
const Schema = require("../schema");
class Mount {
constructor(app) {
app.use(Router("/graphql", graphqlHTTP({
schema: Schema,
graphiql: {headerEditorEnabled: true},
})));
}
}
module.exports = Mount;
我这里将Mount类抛出,在app.js中实例化时将GraphQL作为中间件绑定到Koa中。
下一步将创建GraphQL的Schema,重点来了!
- 第一步
在src/schema
文件夹创建Category.js
。
注⚠️这里插入一下,GraphQL的文档结构主要是按照GraphQLSchema -> query/mutation -> GraphQLObjectType -> fields
结构进行的,当然中间有可能嵌套,后续代码会出现如何进行数据嵌套,这里先解释一下这个结构顺序,最前面GraphQLSchema
代表GraphQL
总表;query/mutation
代表API
的类型,类似于GET/POST
但不绝对;GraphQLObjectType
代表GraphQL的对象类型类型定义,几乎所有 GraphQL 类型都是对象类型。 对象类型有一个名称,但最重要的是描述它们的字段;最后的fields
顾名思义代表的是字段。
#src/schema/Category.js
const categoryController = require("../controller/CategoryController");
const {GraphQLObjectType, GraphQLString, GraphQLList, GraphQLInt, GraphQLBoolean} = require('graphql');
const Resolver = require("./Resolver");
//定义单个分类数据
const SingleCategory = new GraphQLObjectType({
name: 'single_category',
description: '单个分类对象', // 这里写详细点有助于自动生成文档,减少前后端沟通成本
fields() {
return {
"id": {type: GraphQLString},
"level": {type: GraphQLString, defaultValue: "", description: "级别"},
"name": {type: GraphQLString},
"parentId": {type: GraphQLString},
"recommend": {type: GraphQLString, defaultValue: ""},
"recs": {type: GraphQLList(GraphQLString)},
}
}
});
const Extra = new GraphQLObjectType({
name: 'extra',
description: '列表扩展字段',
fields() {
return {
ct: {type: GraphQLString}
}
}
});
const Page = new GraphQLObjectType({
name: 'page',
description: '列表分页子段',
fields() {
return {
totalCount: {type: GraphQLString},
pageSize: {type: GraphQLInt}
}
}
});
const CategoryList = new GraphQLObjectType({
name: 'category_list',
description: '分类对象列表', // 这里写详细点有助于自动生成文档,减少前后端沟通成本
fields() {
return {
success: {type: GraphQLBoolean},
data: {type: new GraphQLList(SingleCategory)},
extra: {type: Extra},
page: {type: Page},
resultCode: {type: GraphQLInt},
}
}
});
//定义分类列表对象
const Category = {
name: 'query_category_list',
type: CategoryList,
args: { //定义参数
platformId: {
name: 'platformId',
type: GraphQLString
},
type: {
name: 'type',
type: GraphQLInt,
}
},
async resolve(root, params, ctx) {
let {body} = await categoryController._lst(ctx, {
platformId: params.platformId,
type: params.type
});
let resolver = new Resolver(body);
return resolver.toJson();
}
}
module.exports = {Category};
这个文件的结构稍显复杂,但大多数都是通用的定义,对象类型定义GraphQLObjectType
,以及字段定义fields
,以及在这里出现的数据嵌套
const SingleCategory = new GraphQLObjectType({//子级
...
});
const CategoryList = new GraphQLObjectType({
...
data: {type: new GraphQLList(SingleCategory)}//父级
...
});
这里首先需要详细解释一下从graphql
中解构出来的GraphQLString, GraphQLList, GraphQLInt, GraphQLBoolean
这几个类型的含义。
-
GraphQLString
GraphQL的字符串类型 -
GraphQLList
GraphQL的列表类型,列表类型是一种类型标记,用来包装另一个类型。在定义一个对象类型的字段时,经常出现(非标量)。 -
GraphQLInt
GraphQL的整型类型 -
GraphQLBoolean
GraphQL的布尔类型
除此之外的标量类型还有 -
GraphQLFloat
GraphQL的浮点类型 -
GraphQLID
GraphQL的ID类型 -
GraphQLObjectType
GraphQL的对象类型定义,name
名称、description
描述、fields
字段函数等。在fields
字段函数中返回对象,每一个对象代表一个字段,每一个字段都可以设置type
数据类型、defaultValue
,默认值、description
描述、以及resolve
对该字段的处理函数,例如一下代码
type: GraphQLString, description: "级别", resolve(obj) {
console.log("级别:", obj.level)
return obj.level || "";
}
resolve
函数亦可以对结果处理或指定默认值等操作
其次,在最后抛出的Category
对象中出现了args
、resolve
、type
:
-
args
代表定义参数,是指GraphQL调用时的入参,对象类型key
指的是参数名称,value
是一个对象,里面分别又定义了名称(name
)和类型(type
),名称
指向外层的参数名称,类型
代表参数类型。 -
resolve
代表异步函数,三个参数分别代表rootValue可以在app.js
实例化GraphQL中指定,指定根节点的值,或者过滤等,在这里就可以从root
参数中取得。params
代表GraphQL在调用时传入的参数名称,与args
参数定义强相关。ctx
为Koa的Context
上下文,在上文中有介绍。 -
type
代表返回的GraphQL实体
这样一个GraphQL的Schema就创建完成了
- 第二步
在src/schema
文件夹下创建index.js,这一步目的是在GraphQLSchema
上挂载刚刚创建的Category
schema。
#src/schema/index.js
const {GraphQLSchema, GraphQLObjectType} = require('graphql');
const {Category} = require("./Category");
//总查询对象
let queryObj = new GraphQLObjectType({
name: 'query',
fields: () => ({
categoryList: Category,
})
});
//GraphQL总表
let schema = new GraphQLSchema({
query: queryObj,
})
module.exports = schema;
这样就跟app.js
的graphqlHTTP
中的schema对应,这样一套流程完成。
- 第三步
为了发挥NodeJs更好的性能在bin
下创建www
文件并引入cluster
模块
#bin/www
#!/usr/bin/env node
const app = require('../app');
const http = require('http');
const cluster = require("cluster");
const EventEmitter = require('events').EventEmitter;
http.globalAgent.maxSockets = process.NODE_MS ? process.NODE_MS : 1000;
http.globalAgent.keepAlive = true;
const numCPUs = require('os').cpus().length;
let instances = process.NODE_INSTANCES || numCPUs;
if (cluster.isMaster) {
console.info(`Master ${process.pid} is running`);
for (let i = 0; i < instances; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.info(`worker ${worker.process.pid} died`);
});
cluster.on('disconnect', worker => {
let w = cluster.fork();
console.warn(`工作进程 ${w.process.pid}重新创建并启动`);
});
} else {
console.info('[worker] ' + "start worker ..." + cluster.worker.id);
let server = http.createServer(app.callback());
server.listen('9999', '0.0.0.0');
server.timeout = 6 * 1000;
const emitter = new EventEmitter();
emitter.setMaxListeners(0);
}
我们知道NodeJs是非阻塞IO单进程的,利用cluster
模块创建集群模式,获取电脑CPU核心数,创建更多child_process
,将NodeJs性能发挥到最大。
将package.json
中增加脚本启动
"description": "node koa2 koa-graphql",
"scripts": {
"dev": "node bin/www"
},
- 第四步
在项目路径下执行npm run dev
,在浏览器窗口访问http://localhost:9999/graphql
进行访问
左侧是查询窗口输入GraphQL查询语句并且给了例子和按钮具体操作的方法,这里先输入我们自己的查询语句以及动态变量
在左上角输入框内输入GraphQL语句
#查询语句
query($platformId:String,$type:Int){
categoryList(platformId:$platformId,type:$type){
success
data {
id
name
level
}
}
}
在左下角的输入框内输入参数变量
#动态变量
{
"platformId":"21312313",
"type":1
}
下图为查询的结果
当然我们一样可以不写变量而是直接指定参数的方式查询,方式如下:
query{
categoryList(platformId:"18511070930",type:1){
success
data {
id
name
parentId
recs
level
recommend
}
}
}
这两种方式怎么方便怎么使用,查询的结果是一样的。
GraphQL语句
在使用GraphQL中语句其实也算是使用难点之一,一下将详细介绍GraphQL语句的使用
Query Mutation
- query
以上面的代码为例,category中含有success
、data
、data
中又含有id
、name
、parentId
等字段。query
作为一个对象,里面包含一个查询categoryList的函数用于查询上述字段 - argument
上面categoryList函数中包含两个参数、分别为platformId
、type
代表查询时要传入的参数。传参按照上面例子中可以有两种方式,一种为直接传参,另一种为动态参数。 - aliases
别名其实就是对返回值的另一种修饰例如
category: categoryList(platformId:"18511070930",type:1){
success
data {
id
name
parentId
recs
level
recommend
}
}
这里的category:
就是一个别名,返回数据时会增加category:
字段,其他字段会在它之下。
- fragments
片段其实就是多数重复字段的一个简化集合,像C的struct以及TS的interface等,下面的例子就形象的说明众多重复的字段可以抽象为一个个fragments。
fragment data on single_category{
name
parentId
recs
level
recommend
}
query{
categoryList(platformId:"18511070930",type:1){
success
data{
...data
}
}
}
- variables
这其实是另外一种传参,单独放到variables
对象中,格式为json
,第一个查询就是个例子,这里不做赘述。有一点需要注意参数必须以$
开头,传入类型必须与定义的类型一致,否则报错。 - meta fields
元字段主要是返回类型用的,例如我们可以用元字段去知道我们当前操作的是哪个数据实体,主要的元字段有__typename
,代码如下
query{
categoryList(platformId:"18511070930",type:1){
success
__typename
data{
...on single_category{
__typename
name
parentId
recs
level
recommend
}
}
}
}
结果如图
- inline fragments
行内片段上面代码例子中已经有了,就是将fragments用法向内并且省略fragments
。
Node GraphQl使用及类型用法说明到此为止,下一回将会说到用TS写GraphQL,以及关于使用apollographql
用法说明等。