写点什么

NodeJs 中使用 Apollo Server 构建 GraphQL API 服务

用户头像
devpoint
关注
发布于: 2021 年 05 月 09 日
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:类型

类型是数据模型的强类型表示,这是一个使用Apollographql-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


谢谢

发布于: 2021 年 05 月 09 日阅读数: 18
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
NodeJs中使用Apollo Server构建GraphQL API服务