本文会简单介绍在前端实践 GraphQL,使用 express
+ express-graphql
+ GraphQL.js
搭建一个简单图书查询、修改、新增的 GraphQL 接口。
简单介绍 GraphQL
- GraphQL 是一个查询语言 + 后端解析查询运行时:它不依赖任何数据库和任何后端实现,仅靠你现有的后端代码和数据存储,按照 GraphQL 的数据定义,就可以构建起来。
- 一个接口搞定所有数据的增删改查:在 GraphQL 服务中进行的数据定义,可以为前端查询提供各种数据的查询的统一的接口。
与传统 Restful API 对比
假设我们有一个业务是查询书名《JAVASCRIT 高级程序设计的》的作者信息。下面对比下使用 Restful API 和 使用 GraphQL 的区别
- 使用 Restful API
- 先通过请求
"/books?name=xxx"
获取到书名《JAVASCRIT 高级程序设计》 的书籍信息,通过代码从书籍信息中获取取到作者 id。 - 再通过请求
"/author?id=xxx"
获取作者详细信息
- 使用 GraphQL
使用例如以下 GraphQL 查询语言可以一个请求完成查询。
{
authorOfBook(bookName:"JAVASCRIT 高级程序设计"){
name
age
address
id
}
}
复制代码
以上是一个 GraphQL 的查询,它看起来就像是一个方法,这个方法可以接收参数( bookName )。这个方法会返回一个 JSON,其中会包含 name、age、address、id 等字段。我们在实际使用 GraphQL 时只需要向 GraphQL 提供的服务接口发起带着这个查询结构的信息就可以获取到我们想要的 Author 信息了。以上示例实际会返回一个作者信息,和查询定义参数保持一致,如下
{
name:"Matt Frisbie",
age:55,
address:"北京市朝阳区",
id:10010011
}
复制代码
如上 GraphQL 的查询参数之所有能工作,是因为在后端 GraphQL 进行了对应的定义,定义主要包含查询的什么字段,字段类型,如何取值。比如在去上面的查询中有 name,age,address,id,这些都需要在 GraphQL 中进行定义。倘若没有定义,对应的查询就会报错。
定义支持的查询类型可以按需进行字段查询(后端 GraphQL 服务会根据查询信息直接只返回前端想要的字段而不是 author 的所有信息),比如只查作者的 name 信息就可以直接这样的查询:
{
author(bookName:"JAVASCRIT 高级程序设计"){
name
}
}
复制代码
返回的值就只会有:
{
name:"Matt Frisbie",
}
复制代码
实践一下
创建一个 node express 项目
通过一个图书查询和修改的 demo 来演示下 GraphQL 的实践,主要涉及查询类型定义、查询参数传递。
创建一个 node express 项目,安装 graphql.js、expess-graphql 依赖 (一个 FaceBook 实现的 GraphQL JS 库)。
mkdir my-first-graphql
cd my-first-graphql
npm init
npm i express graphql express-graphql
复制代码
编写基本 express 的代码
touch index.js
复制代码
写入下面的代码,这里就是配置了一个支持 hello 字段查询(返回 ”hello world“)的 GraphQL 服务。
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { GraphQLSchema } = require('graphql');
const { GraphQLObjectType, GraphQLString } = require('graphql/type');
// 这里就是是定义支持的查询|修改,通过 GraphQL schema 进行定义
var schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'root',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'hello world',
},
},
}),
});
var app = express();
// 这里是进行 GraphQL 服务的入口查询的注册,需要传入 schema
app.use(
'/graphql',
graphqlHTTP({
schema: schema,
graphiql: true,
})
);
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');
复制代码
- GraphQLSchema 构造函数参数需要传入一个对象。该对象中 query: 意思是定义一个查询型的接口,如果想要定义修改或添加的,那么这里会改为 mutation。
- query: 的值需要是一个 GraphQL 类型,表示查询返回的结果的类型,GraphQLObjectType 类型(即对象类型),这里表示支持的查询的是一个对象。
- fields: 用于定义查询支持的字段,即可以通过查询语言查询的字段。
- hello: 配置的查询字段 hello 及类型,它的类型是 String (GraphQLString, 在 GraphQLJS 中,支持的类型都会有自己的封装,例如还支持 Int (GraphQLInt)、List(GraphQLList)、Boolean(GraphQLBoolean) 之类的。
- resolve:指定 hello 查询返回的值
启动 GraphQL 后端服务
node ./index.js
复制代码
打开浏览器到http://localhost:4000/graphql
可以看到 GraphQL 的服务页面了,在这个页面可以直接查看到现有支持的查询字段。在这个页面可以直接进行 GraphQL 的查询。
比如我们的代码支持了 hello 查询,在查询窗口填写如下查询就可以在直接看到 “hello world“ 的结果了。
query{
hello
}
复制代码
创建一个图书信息的数据文件,模拟数据库数据。
touch data.js
复制代码
写入如下内容,这样我们就有了可以查询的数据了。
var autors = [
{
id: 1,
name: '张三',
desc: '擅长科幻小说',
},
{
id: 2,
name: '李四',
desc: '擅长修仙、玄幻小说',
},
{
id: 3,
name: '王五',
desc: '擅长都市、言情小说',
}
]
var books = [
{
id: 1001,
name: '我在未来等你',
autor: 1
},
{
id: 1002,
name: '时间旅行者',
autor: 1
},
{
id: 1003,
name: '问灵山道长',
autor: 2
},
{
id: 1004,
name: '又见桃花源',
autor: 2
},
{
id: 1005,
name: '霸道总裁不爱我',
autor: 3
},
]
module.exports = {
autors,
books
}
复制代码
事件查询所有图书、作者、按作者查询图书、新增图书等示例。
查询所有作者
看到这里,应该可以想到,查询所有图书应该如上面提到的查询 hello 一样要定一个查询类型。因为 hello 的类型是 string,所以不用单独定义。但是我们想要查询的书籍是一种自定义的类型,我们需要定义 authors 这个查询类型。有了类型才可以定义查询接口。
author 和 authors 见如下代码及注释:
// 这里定义了 Author 类型,fileds 里面每一项定义了 Author 类型有的属性及类型
var AuthorType = new GraphQLObjectType({
name: 'Author',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString },
desc: { type: GraphQLString },
},
});
// Author 类型的一个集合类型(List)
var AuthorListType = new GraphQLList(AuthorType);
复制代码
有了类型后 GraphQL 服务就可以知道图书信息中的字段类型了,解下来是定义 schema。 schema 也就是告诉 GraphQL 可以提供怎样的查询方式,见如下代码及注释:
// 根查询,即最外层可用的查询
var RootQueryType = new GraphQLObjectType({
name: 'library',
fields: {
// 这里定义可以查询 authors 这个字段
authors: {
// 该字段的类型是上面定义的 Author 类型的一个集合(List)
type: AuthorListType,
// 取值为我们定义的假数据
resolve: () => authors,
},
},
});
// 定义 schema,即定义 GraphQL 服务支持的查询
const librarySchema = new GraphQLSchema({
// 这里定义查询的根类型
query: RootQueryType,
});
复制代码
以上定义了支持 authors 查询字段, 现在将 express 的 GraphQL 的schema 换成我们刚定义的 librarySchema 就可以了
app.use(
'/graphql',
graphqlHTTP({
schema: librarySchema,
graphiql: true,
})
);
复制代码
来查一下所有的作者看看,如图
我们也可以只查名字,即在查询中只传入 name,如图
查询所有图书
这里就和查询所有作者是一样的了,直接贴代码。
// 这里定义了 Book 类型,fileds 里面每一项定义了 Book 类型有的属性及类型
var BookType = new GraphQLObjectType({
name: 'Book',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString },
author: { type: GraphQLInt },
},
});
// Author 类型的一个集合类型(List)
var BookListType = new GraphQLList(BookType);
// 根查询
var RootQueryType = new GraphQLObjectType({
name: 'library',
fields: {
authors: {
type: AuthorListType,
resolve: () => authors,
},
// 这里定义可以查询 books 这个字段
books: {
// 该字段的类型是上面定义的 Book 类型的一个集合(List)
type: BookListType,
// 取值为我们定义的假数据
resolve: () => books,
},
},
});
复制代码
查询所有图书
查询所有图书和所有作者
查询所有图书书名,和所有作者名
按作者名查询图书
通过以上我们已经可以查询到所有的图书了和作者了。最简单的实现按作者名查询图书的方式,可以是先全查到前端,再在前端进行筛选。
显然这是实际开发中不可能接受的。
现在,我们来支持通过作者名查询图书。首先我们要支持查询传参,然后在 GraphQL 定义查询 field 的 resolve 方法中解析到参数对应的数据。 来看代码和注释
// 根查询
var RootQueryType = new GraphQLObjectType({
name: 'library',
fields: {
authors: {
type: AuthorListType,
resolve: () => authors,
},
books: {
type: BookListType,
resolve: () => books,
},
// 定义一个查询字段,通过该字段查询指定作者的图书信息
booksByAuthor: {
// 返回类型也是一个图书的集合类型,同查询所有图书
type: BookListType,
// 定义查询传参
args: {
// 参数名
name: {
// 参数类型
type: GraphQLString,
},
},
// 如何取值
resolve: (parent, args) => {
// 通过查询参数中 name 字段从作者列表找到作者id
const author = authors.find((author) => author.name === args.name);
// 通过作者 id 找到所有该做作者的图书
const targetBooks = books.filter((b) => b.author === author.id);
return targetBooks;
},
},
},
});
复制代码
这样就可以通过作者名查所有该作者的图书信息了
当然也可以只查张三的图书名
新增图书
最后实现一个插入操作,如最开始提到的,实现插入操作就需要定义个 type 为 muttion 的 schema 了。
const RootMutationType = new GraphQLObjectType({
name: 'updateLibrary',
fields: {
addBook: {
// 定义操作返回值类型
type: BookType,
// 和查询一样定义传参
args: {
name: { type: GraphQLString },
authorId: {type: GraphQLInt },
},
// 定义操作方式,及返回值获取方式
resolve: (parent, args) => {
var b = {
id: books.length + 1000,
name: args.name,
author: args.authorId,
};
// 添加图书
books.push(b);
// 返回图书
return b;
},
},
},
});
// 定义 schema,即定义 GraphQL 服务支持的查询
const librarySchema = new GraphQLSchema({
query: RootQueryType,
// 这里定义修改操作的入口对象类型
mutation: RootMutationType,
});
复制代码
来添加一本图书作者 id 为 1 (张三)的名为 《宇宙的尽头》的图书。可以看到返回了该书添加后的信息,生成图书的 id 为 1005
来查询下所有图书,可以看到已经添加进去了。
用 postman 来查询下
总结
简单介绍 GraphQL 基本概念,通过一个简单的图书查询例子来展示了在 node 下搭建一个 GraphQL 的后端服务。