GraphQL实战(后端篇:apollo-server-koa + koa)

image

前言

什么是GraphQL?

简而言之就是用于API的查询语言。你可以理解为只需要一次请求加上点查询参数,就可以定制任何后端有的信息。

在继续讲GraphQL之前我们先来看看我们常用的RESTFul接口(REST和RESTFul的关系就像Beatuy和Beautiful的关系)的优缺点(没缺点我们换它干啥)

RESTFUL接口的优缺点

RESTFul接口在历史上最大的贡献在于实现了前后端分离以及后端的无状态设计(后端无状态设计就是后端的资源不需要建立session,完全依赖客户端的请求)。

优点

  1. 不仅有http状态码可供错误机制处理还可以方便的自定义错误码
  2. 实现了http缓存,关于Http缓存可以参考这篇文章

缺点 :

  1. 接口冗长。(随着业务的不断丰富,单个RESTFul接口可能变得越来越冗长)
  2. 文档难于维护。(RESTFul文档的维护缺失是个很耗人力的事)
  3. 无法自定义相应结果。(有时候我只需要一个用户名,接口RESTFul给我返回了一大堆不需要的字段)
  4. 效率低下。(如果用RESTFul接口的话,经常会遇到需要几个RESTFul接口才能完成的展示页面的情况)

针对RESTFul的缺点,GraphQL就应运而生了。来看下它的优缺点:
优点

  1. 自定义查询API,体验好。(后端性能是否提高暂且不论,但是前后端代码减少了不少,一个接口搞定所有需要的信息)
  2. 文档自动生成。(再也不需要人工维护文档了,可以往下看,有例子)
  3. 可以自定义查询结果。(接口调用者决定需要哪些字段,后端会根据前端的查询参数返回定制的字段)

缺点

  1. 无http状态码。(无论查询条件是否正确,http状态码返回的都是200,而且自定义的状态码不好处理)
  2. 无缓存(据说Apollo GraphQL这个项目已经集成了,具体插件是apollo-cache-inmemory);

以上大致讲了下GraphQL的一个优缺点,自然也能大致了解其应用场景,在GraphQL还没有完全解决它的缺点之前,我们可以将其和RESTFul接口搭配使用。接下来我们基于koa 2.5.1 和 apollo-server-koa 2.4.8 进行实战演练


由于我这个项目是之前基于RESTFul接口的,这里我将GraphQL集成到上面(也就是在后端返回和前端之间加入GraphQL这层拦截层)。
先看下项目结构


image

总共基于 koa 和 apollo-server-koa 操作有一下6个步骤

  1. 引入插件 apollo-sever-koa
  2. 创建apollo server并传入GraphQL表
  3. 创建koa对象
  4. 将koa对象作为中间件传入apollo server
  5. 监听端口
  6. 定义GraphQL表并传入query对象和Mutation对象

【细节】

安装插件 apollo-sever-koakoa
cnpm install apollo-server-koa koa
创建app.js
const Koa = require('koa');
const {ApolloServer, gql} = require('apollo-server-koa'); // graphql-koa插件
const schema = require('./server/graphql/index.js'); //自定义的GraphQL的表

const server = new ApolloServer({ //创建Graphql server
    schema,
    context: ({ ctx }) => {
        // let token = ctx.
    }
});
server.applyMiddleware({app}); //apollo server使用koa中间件

app.listen(9527, ()=> { //监听端口
    console.log(`server running success at ${server.graphqlPath}`)
})
创建GraphQL表
//index.js

const { articleList } = require('./schemas/articles');
const { postLogin } = require('./schemas/user');


const {
    GraphQLSchema,
    GraphQLObjectType
} = require('graphql');

//总查询对象
let queryObj = new GraphQLObjectType({
    name: 'query',
    fields: () => ({
        articleList: articleList
    })
})

//总体变更对象
let mutationObj = new GraphQLObjectType({
    name: 'Mutation',
    fields: () => ({
        postLogin: postLogin
    })
})
//GraphQL总表
let schema = new GraphQLSchema({
    query: queryObj,
    mutation: mutationObj
})
module.exports = schema
创建文章表
const {
    GraphQLObjectType, //对象类型
    GraphQLString, //字符串类型
    GraphQLList, //数组类型
    GraphQLInt //int类型
} = require('graphql');

//文章业务逻辑控制
const ArticleController = require('../../controller/article.js');

//定义评论对象
let commentType = new GraphQLObjectType({
    name: 'commentType',
    description:'评论对象',
    fields() {
        return {
            name: { type: GraphQLString },
            email: { type: GraphQLString },
            comment_content: { type: GraphQLString }            
        }
    }
})

//定义单个文章对象
let ArticlesType = new GraphQLObjectType({
    name: 'single_article_type',
    description: '单个文章对象', // 这里写详细点有助于自动生成文档,减少前后端沟通成本
    fields() {
        return {
            _id: { type:GraphQLString },
            page_view_time: { type: new GraphQLList(GraphQLString) },
            user_view: { type: new GraphQLList(GraphQLString)},
            comment: { type: new GraphQLList(commentType) },
            page_view_count: { type: GraphQLInt }, 
            md_content: { type: GraphQLString }, 
            html_content: { type: GraphQLString }, 
            update_time: { type: GraphQLString }, 
            create_time: { type: GraphQLString }, 
            title: { type: GraphQLString }, 
            user_view_count: { type: GraphQLInt }, 
            comment_count: { type: GraphQLInt }           
        }
    }
})

//定义文章列表对象
let articleList = {
    name: 'query articles list',
    type: new GraphQLList(ArticlesType),
    args: { //定义参数
        id: {
            name: 'id',
            type: GraphQLString
        },
        limit: {
            name: 'limit',
            type: GraphQLInt,
        },
        skip: {
            name: "skip",
            type: GraphQLInt
        }
    },
    async resolve(root, params, options) {
        let result = await ArticleController.queryArticle({
            id: params.id,
            limit: params.limit,
            skip: params.skip
        });
        return result;
    }
}

module.exports = {
    articleList
}
创建用户表
const {
    GraphQLSchema,
    GraphQLObjectType,
    GraphQLString,
    GraphQLInt
} = require('graphql');


//用户逻辑控制
const UserController = require('../../controller/user.js');

const UserOutputType = new GraphQLObjectType({
    name: 'user_operage_type',
    fields() {
        return {
            name: { type: GraphQLString },
            token: { type: GraphQLString }
        }
    }
})
const postLogin = {
    description: 'postlogin',
    type: UserOutputType,
    args: {
        username: {
            name: 'username',
            type: GraphQLString
        },
        password: {
            name: 'password',
            type: GraphQLString
        }
    },
    async resolve(root, params, options) {
        let user = await UserController.postLogin({
            username: params.username,
            password: params.password
        });
        return user;
    }
}

module.exports = {
    postLogin
}

看效果


test.gif
test2.gif
点击schema就可以查看所有字段,自动生成API文档!
test3.gif

网上很多express + graphql的例子但是基于koa 2.x和 apollo-server-koa 2.x的版本的例子比较少,这里我也是查阅了很多资料和官网实践整理出来的,若有意见或疑问欢迎留言。

参考文献

https://blog.csdn.net/jeffrey20170812/article/details/83348549
//www.greatytc.com/p/2ad286397f7a?open_source=weibo_search
http://web.jobbole.com/94006/
apollo graphql官网

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容