node基于express的GraphQL API服务器

903 阅读8分钟

相信有很多仁兄在2018年底都看到过 2018 JavaScript 现状调查报告 这篇文章。其中有一张图甚是有趣: 可以看得出来 GraphQL 的趋势大好,而且也越来越受欢迎了,似乎不学习要跟不上时代了。既然这样我们就来学习一下怎么用node搭建GraphQL服务端API架构 (本文将会为您讲述最全面的node GraphQL知识, 文章末尾会附带完整的demo供给各位参考)


GraphQL是什么

简单的说明一下GraphQL是Facebook开源的一种规范应用层查询语言,它具有很多优点:客户端可以自定义查询语句,通过自定义不仅提高了灵活性,而且服务端只返回客户端所需要的数据,减少网络的开销,提高了性能;服务端收到客户端的请求,首先做类型检查,及时反馈,而且更加安全;自动生成文档,降低维护成本;服务端通过新增字段,deprecated字段,避免版本的繁杂和紊乱。 附上官方的文档 GraphQL 附上express实现graphql的文档 express-graphql


GraphQL vs RESTful

RESTful:服务端决定有哪些数据获取方式,客户端只能挑选使用,如果数据过于冗余也只能默默接收再对数据进行处理;而数据不能满足需求则需要请求更多的接口。 GraphQL:给客户端自主选择数据内容的能力,客户端完全自主决定获取信息的内容,服务端负责精确的返回目标数据。


##实现GraphQL服务器 首先我们先来实现一个最简单的node GraphQL服务器

#####项目基础环境

mkdir graphql-api
# 进入项目文件夹
cd graphql-api
# 初始化package文件
npm init # 该命令中的所有步骤全部回车

为了方便下面的步骤,我们在graphql-api文件夹中手动创建下面的目录结构,并安装指定的依赖 项目文件结构

# 安装项目依赖
npm install express express-graphql graphql

######index.js代码

const express = require('express')
const expressGraphql = require('express-graphql')
const app = express()
// 配置路由
app.use('/graphql', expressGraphql(req => {
  return {
    schema: require('./graphql/schema'), // graphql相关代码主目录
    graphiql: true // 是否开启可视化工具
    // ... 此处还有很多参数,为了简化文章,此处就一一举出, 具体可以看 刚才开篇提到的 express文档,
    // 也可以在文章末尾拉取项目demo进行查阅
  }
}))
// 服务使用3000端口
app.listen(3000, () => {
  console.log("graphql server is ok");
});

######graphql/schema.js代码

const {
  GraphQLSchema,
  GraphQLObjectType
} = require('graphql')
// 规范写法,声明query(查询类型接口) 和 mutation(修改类型接口)
module.exports = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    description: '查询数据',
    fields: () => ({
      // 查询类型接口方法名称
      fetchObjectData: require('./queries/fetchObjectData')
    })
  }),
  mutation: new GraphQLObjectType({
    name: 'Mutation',
    description: '修改数据',
    fields: () => ({
      // 修改类型接口方法名称
      updateData: require('./mutations/updateData')
    })
  })
})

######graphql/queries/fetchObjectData.js代码 先在graphql/queries文件夹下创建fetchObjectData.js文件, 并填入以下代码

const {
  GraphQLID,
  GraphQLInt,
  GraphQLFloat,
  GraphQLString,
  GraphQLBoolean,
  GraphQLNonNull,
  GraphQLObjectType
} = require('graphql')
// 定义接口返回的数据结构
const userType = new GraphQLObjectType({
  name: 'userItem',
  description: '用户信息',
  fields: () => ({
    id: {
      type: GraphQLID,
      description: '数据唯一标识'
    },
    username: {
      type: GraphQLString,
      description: '用户名'
    },
    age: {
      type: GraphQLInt,
      description: '年龄'
    },
    height: {
      type: GraphQLFloat,
      description: '身高'
    },
    isMarried: {
      type: GraphQLBoolean,
      description: '是否已婚',
      deprecationReason: "这个字段现在不需要了"
    }
  })
})
// 定义接口
module.exports = {
  type: userType,
  description: 'object类型数据例子',
  // 定义接口传参的数据结构
  args: {
    isReturn: {
      type: new GraphQLNonNull(GraphQLBoolean),
      description: '是否返回数据'
    }
  },
  resolve: (root, params, context) => {
    const { isReturn } = params
    if (isReturn) {
      // 返回的数据与前面定义的返回数据结构一致
      return {
        "id": "5bce2b8c7fde05hytsdsc12c",
        "username": "Davis",
        "age": 23,
        "height": 190.5,
        "isMarried": true
      }
    } else {
      return null
    }
  }
}

######graphql/mutations/updateData.js代码 先在graphql/mutations文件夹下创建updateData.js文件, 并填入以下代码

const {
  GraphQLInt
} = require('graphql')

let count = 0

module.exports = {
  type: GraphQLInt,  // 定义返回的数据类型
  description: '修改例子',
  args: {  // 定义传参的数据结构
    num: {
      type: GraphQLInt,
      description: '数量'
    }
  },
  resolve: (root, params) => {
    let { num } = params
    count += num
    return count
  }
}

好了,到此为止,简单的GraphQL服务器就搭建好了,让我们来启动看看

node index.js  // 启动项目

然后我们在浏览器打开 http://localhost:3000/graphql 如下图所示

我们可以看到页面分为3栏,左边的是调用api用的,中间是调用api返回的结果 右边实际上就是我们刚才定义接口相关的东西,也就是api文档。 我们在左边粘贴以下代码:

query fetchObjectData {
  fetchObjectData(
    isReturn: true
  ) {
    id
    username
  	age
    height
    isMarried
  }
}

mutation updateData {
  updateData(
    num: 2
  )
}

我们点一下左上角的按钮,会发现我们刚才左边填的2个方法名都在这列出来了,我们分别选择 fetchObjectData 和 updateData api返回结果如下: fetchObjectData 结果 updateData 结果

自此,我们从创建的GraphQL服务器就算完成了,代码也能正常运行,是否有些小激动!! 别着急,正所谓授人以鱼不如授人以渔,接下来我们细细分析一下我们刚才填写的GraphQL代码,了解其规范语法,这样我们才能真正的掌握GraphQL。

##语法规范解析 以下的代码仅为诠释一下GraphQL的规范用法 不要直接拷贝到前面所述的代码中!! 不要直接拷贝到前面所述的代码中!! 不要直接拷贝到前面所述的代码中!!

1、导入GraphQL.js及类型 graphql 无论在定义接口参数和接口返回结果时, 都需要先定义好其中所包含数据结构的类型, 这不难理解,可以理解为我们定义的就是数据模型,其中常用的类型如下。

const {
    GraphQLList,  // 数组列表
    GraphQLObjectType, // 对象
    GraphQLString, // 字符串
    GraphQLInt,  // int类型
    GraphQLFloat,  // float类型
    GraphQLEnumType,  // 枚举类型
    GraphQLNonNull,  // 非空类型
    GraphQLSchema // schema(定义接口时使用)
} = require('graphql')

2、定义schema schema实例中,一般规范为 query: 定义查询类的接口 mutation: 定义修改类的接口

new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query', // 查询实例名称
    description: '查询数据', // 接口描述
    fields: () => ({
      // 查询类型接口方法名称
      fetchDataApi1: require('./queries/fetchDataApi1'),
      fetchDataApi2: require('./queries/fetchDataApi2'),
      fetchDataApi3: require('./queries/fetchDataApi3'),
      ...
    })
  }),
  mutation: new GraphQLObjectType({
    name: 'Mutation',
    description: '修改数据',
    fields: () => ({
      // 修改类型接口方法名称
      updateDataApi1: require('./mutations/updateDataApi1'),
      updateDataApi2: require('./mutations/updateDataApi2'),
      ...
    })
  })
})

3、接口方法定义

// 引用需要用到的数据类型
const {
  GraphQLID,
  GraphQLString,
  GraphQLNonNull,
  GraphQLObjectType
} = require('graphql')
// 第一部分 定义接口返回的数据结构
// 不难看出来,下面定义的是
/*
   {
      id
      username
   }
*/
const userType = new GraphQLObjectType({
  name: 'userItem',
  description: '用户信息',
  fields: () => ({
    id: {
      type: GraphQLID,
      description: '数据唯一标识'
    },
    username: {
      type: GraphQLString,
      description: '用户名'
    }
  })
})
// 第二部分 定义接口
module.exports = {
  type: userType,
  description: 'object类型数据例子',
  // 定义接口传参的数据结构
  args: {
    isReturn: {
      type: new GraphQLNonNull(GraphQLBoolean),
      description: '是否返回数据'
    }
  },
  resolve: (root, params, context) => {
    const { isReturn } = params
    // 返回的数据与前面定义的返回数据结构一致
    return {
       "id": "5bce2b8c7fde05hytsdsc12c",
       "username": "Davis"
    }
  }
}

我们来分析一下上图中第一部分的内容,GraphQLObjectType是GraphQL.js定义的对象类型,包括name、description 和fields三个属性: name: 可作为对象的全局唯一名称 description: 是对象的描述 fields: 是解析函数也就是定义具体数据结构, 该对象包含什么键值

我们再来看一下第二部分的内容 type: 代表这个接口需要返回的数据结构是什么 description: 接口的描述 args: 接口的传参数据结构定义 resolve: 接口内部具体的实现逻辑(需求代码都写在这里面) 我们在处理完业务逻辑之后只需要返回与 type 中定义的数据结构一样的数据即可。

相信这时候你会发现resolve中又有三个参数是什么鬼,稳住,不要慌,这时候我们看回一开始我们的index.js文件 index.js部分代码截图 省略号这个地方是可以再定义很多参数的,而 rootValue和context这两个参数正好就对应了接口resolve方法中的root和resolve,我们可以看看官方文档对这两个参数的解释: 一般我们可以通过rootValue来做api认证,context可以传递用户信息、数据库链接等,也可以不用,这完全取决于你的业务场景

总结

1、简单点说,其实我们在编写GraphQL 代码的时候,你可以理解为他跟typescript有些相似,都需要事先定义好传参的数据结构,返回结果的数据结构,虽然这个比方不合理,但是这样讲更通俗易懂些 2、description 虽然不是必填的,但是建议都写上,因为它的作用其实就是api文档的解释。 3、GraphQL 真的很强大,虽然一开始接触的时候会有点蒙,啥玩意???这定义那定义的,虽然比RESTful风格的api上手难理解很多,但是我相信,当你学会使用GraphQL的时候,你会发现这玩意还是挺有意思的。 4、GraphQL 的用法还有很多,为了文章尽量简单易懂, 本文只是列出了其中一种用法,在我的demo中还有更多的用法,比如GraphQLEnumType(枚举类型)、GraphQLUnionType(联合类型)、GraphQLInterfaceType(接口类型)...有兴趣的看一下。

附上本人完整的demo: graphql-api