NodeJs中使用Apollo Server构建GraphQL API服务
GraphQL是一种通过强类型查询语言构建api的新方法。GraphQL于2015年由Facebook发布,目前正迅速获得关注,并被Twitter和Github等其他大型公司所采用,之前写过一篇《浅谈NodeJS搭建GraphQL API服务》只是简单介绍构建API。在本文中,我们将介绍如何使用Apollo Server在Node.js中设置GraphQL服务器。
服务器上GraphQL的高级概述
一旦熟悉了所有的活动部件,GraphQL的上手实际上就非常简单。GraphQL服务是通过一个模式定义的,其工作原理大致如下:
Types:类型
类型是数据模型的强类型表示,这是一个使用Apollo
的graphql-tools定义的帖子类型的示例,在本教程中将使用它来定义架构。
import User from "./user_type";
const Post = `
type Post {
id: Int!
title: String!
body: String!
author_id: Int!
author: User
}
`;
export default () => [Post, User];
Queries:查询
查询是定义可以针对架构运行哪些查询的方式,这是模式的RootQuery
中的一个查询的示例;
const RootQuery = `
type RootQuery {
posts: [Post]
post(id:Int!): Post
users: [User]
user(id:Int!): User
}
`;
Mutations:更改
更改(Mutations
)类似于post请求(尽管它们实际上只是查询的同步版本),它们允许将数据发送到服务器以执行插入、更新或者删除。下面是一个为新博客文章定义更改(Mutations
)的例子,它接受输入类型PostInput
并将新创建的文章作为post类型返回。
const RootMutation = `
type RootMutation {
createPost(input: PostInput!): Post
}
`;
Subscriptions:订阅
订阅允许通过GraphQL订阅服务器发布实时事件,下面定义了一个订阅的示例:
const RootSubscription = `
type RootSubscription {
postAdded(title: String): Post
}
`;
现在,可以通过在createPost
突变解析器中运行此事件,将事件发布到订阅的事件。
pubsub.publish(‘postAdded’, { postAdded: post });
Resolvers:解析器
解析器是执行操作以响应查询、变异或订阅的地方,在这里,可以进入数据库层执行CRUD操作并返回适当的响应。如下的示例:
resolvers: {
RootQuery: {
posts: () => posts,
post: async (_, { id }) =>
await Post.query()
},
RootMutations: {
createPost: async (_, { input }) =>
await Post.query.insert(input)
},
RootSubscriptions: {
postAdded: {
subscribe: () =>
pubsub.asyncIterator('postAdded')
},
Post: {
author: async post =>
await User.query().where("id", "=", post.author_id)
}
}
Schema:模式
模式(Schema)是将所有活动部分连接在一起,构建服务的API。
开始进入项目
如果想要查看的代码,请在此处找到一个仓库。
安装依赖
首先创建一个项目,这里命名为:graphql-hello-api
mkdir graphql-hello-api
然后进入目录,执行一下命令:
yarn init
添加必须的依赖:
yarn add apollo-server graphql
创建Hello
创建一个名为src
的文件夹,为了更好展示整个过程,不同的示例命名为不同的文件名称,先来创建一个文件:index001.js
。
首先定义了一个查询类型:
const typeDefs = gql`
type Query {
hello: String
}
`;
接下来定义解析器(或GraphQL教程中的根)来解析给定的查询:
const resolvers = {
Query: {
hello: () => {
return "Hello World!";
},
},
};
最后,实例化ApolloServer,然后启动服务。
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
index001.js
的所有代码如下:
const { ApolloServer, gql } = require("apollo-server");
const typeDefs = gql`
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => {
return "Hello World!";
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
下面我们来启动GraphQL Server,进入文件夹src
,执行如下命令,打开浏览器,输入http://localhost:3005/
:
node index001.js
将看下如下界面:
按照上面的图的步骤,录入定义的查询{hello}
,运行结果如下:
GraphQL查询的基本类型可以由字符串、整数、浮点数、布尔值和ID及其列表
[]
组成,下面开始添加一些逻辑代码。
在这里,使用不同的类型如下定义typeDefs
。这!
表示不可为空的结果。接下来我们创建index002.js
,定义3个查询,分别为字符串、浮点数和[]。
const typeDefs = gql`
type Query {
today: String
random: Float!
fibonacci: [Int]
}
`;
相应地设置解析器,如下:
const resolvers = {
Query: {
today: () => {
return new Date().toDateString();
},
random: () => {
return Math.random();
},
fibonacci: () => {
return fibonacci(10);
},
},
};
现在可以看看完整的代码,即index.js
的完整代码:
const { ApolloServer, gql } = require("apollo-server");
const fibonacci = (length) => {
let nums = [0, 1];
for (let i = 2; i <= length; i++) {
nums[i] = nums[i - 1] + nums[i - 2];
}
return nums;
};
const typeDefs = gql`
type Query {
today: String
random: Float!
fibonacci: [Int]
}
`;
const resolvers = {
Query: {
today: () => {
return new Date().toDateString();
},
random: () => {
return Math.random();
},
fibonacci: () => {
return fibonacci(10);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
运行结果如下:
传递参数
现在来展示如何使用查询将一些参数传递给服务器,创建index003.js
,本示例我们将定义查询获取一个指定长度的斐波那契数组,定义参数length
。代码如下:
const typeDefs = gql`
type Query {
fibonacci(length:Int!): [Int]
}
`;
接下来就是解析器,请注意,使用Apollo Server时,它的API于GraphQL API略有不同。参数通过第二个参数传递,格式为:fibonacci: (_, { length })
,这里暂时忽略带有_
的第一个参数。
const resolvers = {
Query: {
fibonacci: (_, { length }) => {
return fibonacci(length);
},
},
};
这里是完整的代码:
const { ApolloServer, gql } = require("apollo-server");
const fibonacci = (length) => {
let nums = [0, 1];
for (let i = 2; i <= length; i++) {
nums[i] = nums[i - 1] + nums[i - 2];
}
return nums;
};
const typeDefs = gql`
type Query {
fibonacci(length: Int!): [Int]
}
`;
const resolvers = {
Query: {
fibonacci: (_, { length }) => {
return fibonacci(length);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
在左边窗口输入查询:
{
fibonacci(length:10)
}
运行结果如下:
对象类型
有时需要返回一个由基本类型构造的更复杂的对象,可以通过为它声明一个类(JavaScript ES6)类型来实现,新建一个文件index004.js
,完整代码如下:
const { ApolloServer, gql } = require("apollo-server");
/**
* 定义一个基础查询,返回查询RandomDie
*/
const typeDefs = gql`
type RandomDie {
numSides: Int!
rollOnce: Int!
roll(numRolls: Int!): [Int]
}
type Query {
getDie(numSides: Int): RandomDie
}
`;
class RandomDie {
constructor(numSides) {
this.numSides = numSides;
}
rollOnce() {
return 1 + Math.floor(Math.random() * this.numSides);
}
roll({ numRolls }) {
const output = [];
for (let i = 0; i < numRolls; i++) {
output.push(this.rollOnce());
}
return output;
}
}
const resolvers = {
Query: {
getDie: (_, { numSides }) => {
return new RandomDie(numSides || 6);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
在录入查询的时候就当调用getDie作为基础查询,如下:
{
getDie(numSides:6){
numSides,
rollOnce,
roll(numRolls:10)
}
}
运行结果如下:
使用mutation
前面介绍了Mutations:更改,如果要修改服务器端数据,需要使用mutation
代替query
,创建index005.js
。
const { ApolloServer, gql } = require("apollo-server");
const fakeDb = {};
const typeDefs = gql`
type Mutation {
setTitle(title: String): String
}
type Query {
getTitle: String
}
`;
const resolvers = {
Mutation: {
setTitle: (_, { title }) => {
fakeDb.title = title;
return title;
},
},
Query: {
getTitle: () => {
return fakeDb.title;
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
输入查询:
mutation{
setTitle(title:"Hello DevPoint!")
}
执行结果如下:
输入类型
有时希望将同类信息设计在一个对象里面进行维护或者规范输入,可以按照接口的方式定义输入类型结构,创建 index006.js
,实现一个维护网站基本信息的示例,整体代码如下:
const { ApolloServer, gql } = require("apollo-server");
const { nanoid } = require("nanoid");
const typeDefs = gql`
input SiteInput {
title: String
author: String
url: String
}
type SiteDetail {
id: ID!
title: String
author: String
url: String
}
type Query {
getSite(id: ID!): SiteDetail
}
type Mutation {
createSite(input: SiteInput): SiteDetail
updateSite(id: ID!, input: SiteInput): SiteDetail
}
`;
class SiteDetail {
constructor(id, { author, title, url }) {
this.id = id;
this.title = title;
this.author = author;
this.url = url;
}
}
const fakeDb = {};
const resolvers = {
Mutation: {
createSite: (_, { input }) => {
var id = nanoid();
fakeDb[id] = input;
return new SiteDetail(id, input);
},
updateSite: (_, { id, input }) => {
if (!fakeDb[id]) {
throw new Error("信息不存在 " + id);
}
fakeDb[id] = input;
return new SiteDetail(id, input);
},
},
Query: {
getSite: (_, { id }) => {
if (!fakeDb[id]) {
throw new Error("信息不存在 " + id);
}
return new SiteDetail(id, fakeDb[id]);
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(3005).then(({ url }) => {
console.log(`🚀 GraphQL Server ready at ${url}`);
});
执行node index006.js
运行,输入一下语句创建一个site信息对象,并查询新创建对象的ID:
mutation{
createSite(input:{
title:"DevPoint",
author:"QuintionTang",
url:"https://www.devpoint.com"}
){
id
}
}
运行结果如下:
接下来根据返回的ID,查询信息:
query{
getSite(id:"w3pFxgiCyHgZ8vF6ip1D2"){
id,
author,
title,
author
}
}
运行结果如下:
执行更新操作并查询最新数数据:
mutation{
updateSite(id:"w3pFxgiCyHgZ8vF6ip1D2",input:{
title:"DevPoint WebSite"
}){
id,
title,
author
}
}
运行结果如下:
验证
之前有朋友问到是否有统一验证的地方。
GraphQL有没有统一的入口可以验证参数的有效性?
答案是有的,可以使用GraphQL的context
在HTTP服务器和GraphQL服务器之间实现身份验证。通过自定义context
构建功能,实现请求及用户权限的验证。本文只是简单介绍一下,下期专门写一遍GraphQL中的身份及请求合法性验证文章。
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 在这里进行请求验证
const author = "QuintionTang";
return { author };
},
});
上面所有代码都提交到Github上了,https://github.com/QuintionTang/graphql-hello-api。