Node GraphQL

书接上回,上一章节介绍了什么是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中增加了相关依赖如下图

package.json

创建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对象中出现了argsresolvetype

  • 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上挂载刚刚创建的Categoryschema。
#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.jsgraphqlHTTP中的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查询语句并且给了例子和按钮具体操作的方法,这里先输入我们自己的查询语句以及动态变量
    在左上角输入框内输入GraphQL语句
#查询语句
query($platformId:String,$type:Int){
  categoryList(platformId:$platformId,type:$type){
   success
   data {
    id
    name
    level
   } 
  }
}

在左下角的输入框内输入参数变量

#动态变量
{
  "platformId":"21312313",
  "type":1
}

下图为查询的结果


GraphQL语句和结果

当然我们一样可以不写变量而是直接指定参数的方式查询,方式如下:

query{
  categoryList(platformId:"18511070930",type:1){
    success
    data {
      id
      name
      parentId
      recs
      level
      recommend
    }
  }
}

这两种方式怎么方便怎么使用,查询的结果是一样的。


GraphQL语句

在使用GraphQL中语句其实也算是使用难点之一,一下将详细介绍GraphQL语句的使用

Query Mutation
  • query
    以上面的代码为例,category中含有successdatadata中又含有idnameparentId等字段。query作为一个对象,里面包含一个查询categoryList的函数用于查询上述字段
  • argument
    上面categoryList函数中包含两个参数、分别为platformIdtype代表查询时要传入的参数。传参按照上面例子中可以有两种方式,一种为直接传参,另一种为动态参数。
  • 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用法说明等。

你可能感兴趣的:(Node GraphQL)