[译] 使用 NodeJS 创建一个 GraphQL 服务器

14,070 阅读15分钟

Hello World!在这个 GraphQL 的教程中,你可以学到如何使用 Apollo Server 库 2.0 版本来构建一个基于 NodeJS 和 Experss 的 GraphQL 服务器。

当谈到客户端和应用程序服务器之间的网络请求时,REST(表现层状态转换的代表)是连接二者最常用的选择之一。在 REST API 的世界中,一切都围绕着如何把资源作为可访问的 URL。然后我们会进行 CURD 操作(新建、读取、更新、删除),这些操作是 HTTP 的基本方法,如 GET、POST、PUT 和 DELETE,来与数据进行交互。

这是一个典型的 REST 请求的例子:

// 请求示例
https://swapi.co/api/people/

// 上面请求的 JSON 格式响应
{
	"results": [
		{
			"name": "Luke Skywalker",
			"gender": "male",
			"homeworld": "https://swapi.co/api/planets/1/",
			"films": [
				"https://swapi.co/api/films/2/",
				"https://swapi.co/api/films/6/",
				"https://swapi.co/api/films/3/",
				"https://swapi.co/api/films/1/",
				"https://swapi.co/api/films/7/"
			],
    }
		{
			"name": "C-3PO",
			"gender": "n/a",
			"homeworld": "https://swapi.co/api/planets/1/",
			"films": [
				"https://swapi.co/api/films/2/",
				"https://swapi.co/api/films/5/",
				"https://swapi.co/api/films/4/",
				"https://swapi.co/api/films/6/",
				"https://swapi.co/api/films/3/",
				"https://swapi.co/api/films/1/"
			],
		}
  ]
}

REST API 的响应格式未必会是 JSON,但是这是目前大多数 API 的首选方法。除了 REST,还出现了另一种处理网络请求的方法:GraphQL。它于 2015 年开源,正在改变着开发人员在服务器端编写API以及在客户端处理API的方式。并由 Facebook 开发并积极维护。

REST 的弊端

GraphQL 是一种用于开发 API 的查询语言。和 REST(一种架构或者“一种做事方式”)相比,GraphQL 的开发基于一个理念:客户端每次仅从服务端请求所需要的项目集合。

在上面的例子中,使用了 REST 或者其他类似架构。我们请求 Star Wars 系列电影中 Luke Skywalker 出现过的电影时,我们得到了一系列的 电影 或者 homeworld 的名称,他们还包含了不同的 API URL,引导我们去了解不同 JSON 数据集的详细信息。这肯定是一个过度获取(over fetching)的例子。客户端为了去获取人物 Luke Skywalker 出现在电影中的详情以及他家乡星球的名称,只能去向服务端发起多个请求。

使用 GraphQL,就可以将其解析为单个网络请求。转到 API 网址:https://graphql.github.io/swapi-graphql/,查看运行以下查询(query)看看。

注意:在下面的例子中,你可以不必理会 GraphQL API 幕后的工作方式。我将在本教程后面逐步构建你自己的(可能是第一个)GraphQL API。

{
	allPeople {
		edges {
			node {
				name
				gender
				homeworld {
					name
				}
				filmConnection {
					edges {
						node {
							title
						}
					}
				}
			}
		}
	}
}

我们将获取我们需要的数据。例如角色的名称、他们的性别(gender)、家园(homeworld),以及他们出现的电影(films)的标题。运行上述查询,你将获得以下结果:

{
	"data": {
		"allPeople": {
			"edges": [
				{
					"node": {
						"name": "Luke Skywalker",
						"gender": "male",
						"homeworld": {
							"name": "Tatooine"
						},
						"filmConnection": {
							"edges": [
								{
									"node": {
										"title": "A New Hope"
									}
								},
								{
									"node": {
										"title": "The Empire Strikes Back"
									}
								},
								{
									"node": {
										"title": "Return of the Jedi"
									}
								},
								{
									"node": {
										"title": "Revenge of the Sith"
									}
								},
								{
									"node": {
										"title": "The Force Awakens"
									}
								}
							]
						}
					}
				},
				{
					"node": {
						"name": "C-3PO",
						"gender": "n/a",
						"homeworld": {
							"name": "Tatooine"
						},
						"filmConnection": {
							"edges": [
								{
									"node": {
										"title": "A New Hope"
									}
								},
								{
									"node": {
										"title": "The Empire Strikes Back"
									}
								},
								{
									"node": {
										"title": "Return of the Jedi"
									}
								},
								{
									"node": {
										"title": "The Phantom Menace"
									}
								},
								{
									"node": {
										"title": "Attack of the Clones"
									}
								},
								{
									"node": {
										"title": "Revenge of the Sith"
									}
								}
							]
						}
					}
				}
			]
		}
	}
}

如果应用程序的客户端正在触发上述 GraphQL URL,它只需要在网络上发一个请求就可以得到所需结果。从而消除了任何会导致过度获取或发送多个请求的可能性。

先决条件

要学习本课程,你只需要在本地计算机上安装 nodejsnpm 即可。

GraphQL 简述

简而言之,GraphQL 是一种用于阐述如何请求 data 的语法,通常用于从客户端检索数据(也称为 query)或者对其进行更改(也称为 mutation)。

GraphQL 几乎没有什么定义特征:

  • 它允许客户端准确指定所需的数据。这也称为声明性数据提取。
  • 对网络层没有特殊要求
  • 更容易组合来自多个源的数据
  • 在以 schema 和 query 的形式声明数据结构时,它使用强类型系统。这有助于在发送网络请求之前校验查询。

GraphQL API 的构建模块

GraphQL API 有四个构建模块:

  • schema
  • query
  • mutations
  • resolvers

Schema 以对象的形式在服务器上定义。每个对象对应于数据类型,以便于去查询他们。例如:

type User {
	id: ID!
	name: String
	age: Int
}

上面的 schema 定义了一个用户对象的样子。其中必需的字段 id! 符号标识。还包含其他字段,例如 string 类型的 nameinteger 类型的 age。这也会在查询数据的时候对 schema 进行验证。

Queries 是你用来向 GraphQL API 发出请求的方法。例如,在我们上面的示例中,就像我们获取 Star Wars 相关的数据时那样。让我们简化一下,如果在 GraphQL 中查询,就是在查询对象的特定字段。例如,使用上面相同的 API,我们能获取 Star Wars 中所有角色的名称。下面你可以看到差异,在图片的左侧是查询,右侧是结果。(译者注:原文是 on the right-hand side is the image,译者认为不是很合适)

使用 GraphQL 查询的好处是它们可以嵌套到你想要的深度。这在 REST API 中很难做到。(在 REST API 中)操作变得复杂得多。

下面是一个更复杂的嵌套查询示例:

Mutations: 在 REST 架构中,要修改数据,我们要么使用 POST 来添加数据,要么使用 PUT 来更新现有字段的数据。在 GraphQL 中,整体的概念是类似的。你可以发送一个 query 来在服务端执行写入操作。但是。这种形式的查询称为 Mutation。

Resolvers 是 schema 和 data 之间的纽带。它们提供可用于通过不同操作与数据库交互的功能。

在这个教程中,你将学习用我们刚刚学到的构件,来使用 Nodejs 构建 GraphQL 服务器。

Hello World!使用 GraphQL

现在我们来写我们第一个 GraphQL 服务器。本教程中,我们将使用 Apollo Server。我们需要为 Apollo Server 安装三个包才能使用现有的 Express 应用程序作为中间件。Apollo Server 的优点在于它可以与 Node.js 的几个流行框架一起使用:Express、KoaHapi。Apollo 本身和库无关,因此在客户端和服务器应用程序中,它可以和许多第三方库连接。

打开你的终端安装以下依赖:

# 首先新建一个空文件夹
mkdir apollo-express-demo

# 然后初始化
npm init -y

# 安装需要的依赖
npm install --save graphql apollo-server-express express

让我们简要了解下这些依赖的作用。

  • graphql 是一个支持库,并且在我们这里是一个必要的模块
  • 添加到现有应用程序中的 apollp-server-express 是相应的 HTTP 服务器支持包
  • express 是 Nodejs 的 web 框架

你可以在下面的图中看到我安装了全部的依赖,没有出现任何错误。

在你项目的根路径下,新建一个名字为 index.js,包含以下代码的文件。

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const typeDefs = gql`
	type Query {
		hello: String
	}
`;

const resolvers = {
	Query: {
		hello: () => 'Hello world!'
	}
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
	console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
);

这是我们服务器文件的起点。开始我们仅仅只需要 express 模块。gql 是一个模板文字标记,用于将 GraphQL schema 编写为类型。schema 由类型定义组成,并且强制包含一个用于读取数据的 Query 类型,用于读取数据。它还可以包含表示其他数据字段的字段和嵌套字段。在我们上面的例子中,我们定义了 typeDefs 来编写 graphQL 的 schema。

然后 resolvers 映入眼帘。Resolver 用于从 schema 中返回字段的数据。在我们的示例中,我们定义了一个 resolver,它将函数 hello() 映射到我们的 schema 上的实现。接下来,我们创建一个 server,它使用 ApolloServer 类来实例化并启动服务器。由于我们使用了 Express,所以我们需要集成 ApolloServer 类。通过 applyMiddleware() 作为 app 来传递它,来添加 Apollo Server 的中间件。这里的 app 是 Express 的一个实例,代表了现有的应用程序。

最后,我们使用 Express 模块提供的 app.listen() 来引导服务器。要运行服务器,只需要打开 terminal 并运行命令 node index.js。现在,从浏览器窗口访问 url:http://localhost:4000/graphql 来看看它的操作。

Apollo Server 为你设置了 GraphQL Playground,供你快速开始运行 query,探索 schema,如下所示。

要运行一个 query,在左侧编辑空白部分,输入以下 query。然后按中间的 ▶ (play)按钮。

右侧的 schema 卡描述了我们查询 hello 的数据类型。这直接来自我们服务器中定义的 typeDefs

!你刚创建了第一个 GraphQL 服务器。现在让我们拓展下我们对现实世界的认知。

使用 GraphQL 构建 API

目前为止我们整理了所有必要的模块以及随附的必要术语。在这一节,我们将用 Apollo Server 为我们的演示去创建一个小的 Star Wars API。你可能已经猜到了 Apollo server 是一个库,可以帮助你使用 Nodejs 将 GraphQL schema 连接到 HTTP server。它不局限于特定的 Node 框架。例如上一节中我们使用了 ExpressJS。Apollo Server 支持 Koa,Restify,Hapi 和 Lambda。对于我们的 API,我们继续使用 Express。

使用 Babel 进行编译

如果想从头开始,请继续。从 Hello World! With GraphQL 一节安装所有的库。这是我们在前面一节中安装的所有依赖:

"dependencies": {
		"apollo-server-express": "^2.1.0",
		"express": "^4.16.4",
		"graphql": "^14.0.2"
	}

我将使用相同的项目和相同的文件 index.js 去引导服务器启动。但是在我们构建我们的 API 之前,我想告诉你如何在我们的演示项目中使用 ES6 modules。对于使用像 React 和 Angular 这样的前端库,他们已经支持了 ES6 特性。例如 importexport default 这样的语句。Nodejs 版本 8.x.x 解决了这个问题。我们所需要的只是一个转换器(transpiler)让我们使用 ES6 特性编写 JavaScript。你完全可以跳过这个步骤使用旧的 require() 语句。

那么什么是转换器呢?

转换器(Transpiler)也被称作‘源到源的编译器’,从一种编程语言写的源码中读取代码转换成另一种语言的等效代码。

在 Nodejs 的情况下,我们不会切换编程语言,而是要使用哪些我目前使用的 LTS 版本的 Node 不支持的语言的新特性。我将安装 Babel 编译器,并通过接下来的配置过程在我们的项目中启用它。

首先,你需要安装一些依赖,记得使用 -D 参数。因为我们只会在开发环境中用到这些依赖。

npm install -D babel-cli babel-preset-env babel-watch

只要你成功安装了他们,在项目的根目录下添加一个 .babelrc 文件并且添加以下配置:

{
	"presets": [env]
}

配置流程的最后一步是在 package.json 中添加一个 dev 脚本(script)。一旦(项目文件)发生变化,babel 编译器将自动运行。这由 babel-watch 完成。同时它也负责重新启动 Nodejs 网络服务器。

"scripts": {
	"dev": "babel-watch index.js"
}

要查看它的操作,请将以下代码添加到 index.js 中,看看是否一切正常。

import express from 'express';

const app = express();

app.get('/', (req, res) => res.send('Babel Working!'));

app.listen({ port: 4000 }, () =>
	console.log(`🚀 Server ready at http://localhost:4000`)
);

在终端中输入 npm run dev,不出意外,你可以看到下面的信息:

你也可以在浏览器中访问 http://localhost:4000/ 去看看其操作。

添加 Schema

我们需要一个 schema 来启动我们的 GraphQL API。让我们在 api 目录下创建一个名字为 api/schema.js 的新文件。添加以下 schema。

import { gql } from 'apollo-server-express';

const typeDefs = gql`
	type Person {
		id: Int
		name: String
		gender: String
		homeworld: String
	}
	type Query {
		allPeople: [Person]
		person(id: Int!): Person
	}
`;

export default typeDefs;

我们的 schema 一共包含两个 query。第一个是 allPeople,通过它我们可以列出到 API 中的所有的人物。第二个查询 person 是使用他们的 id 检索一个人。这两种查询类型都依赖于一个名为 Person 对象的自定义类型,该对象包含四个属性。

添加 Resolver

我们已经了解了 resolver 的重要性。它基于一种简单的机制,去关联 schema 和 data。Resolver 是包含 query 或者 mutation 背后的逻辑和函数。然后使用它们来检索数据并在相关请求上返回。

如果在使用 Express 之前构建了服务器,则可以将 resolver 视为控制器,其中每一个控制器都是针对特定路由构建。由于我们不在服务器后面使用数据库,因此我们必须提供一些虚拟数据来模拟我们的 API。

创建一个名为 resolvers.js 的新文件并添加下面的文件。

const defaultData = [
	{
		id: 1,
		name: 'Luke SkyWaler',
		gender: 'male',
		homeworld: 'Tattoine'
	},
	{
		id: 2,
		name: 'C-3PO',
		gender: 'bot',
		homeworld: 'Tattoine'
	}
];

const resolvers = {
	Query: {
		allPeople: () => {
			return defaultData;
		},
		person: (root, { id }) => {
			return defaultData.filter(character => {
				return (character.id = id);
			})[0];
		}
	}
};

export default resolvers;

首先,我们定义 defaultData 数组,其中包含 Star Wars 中两个人物的详细信息。根据我们的 schema,数组中的这两个对象都有四个属性。接下来是我们的 resolvers 对象,它包含两个函数。这里可以使用 allPeople() 来检索 defaultData 数组中的所有数据。person() 箭头函数使用参数 id 来检索具有请求 ID 的 person 对象。这个已经在我们的查询中定义了。

你必须导出 resolver 和 schema 对象才能将它们与 Apollo Server 中间件一起使用。

实现服务器

现在我们定义了 schema 和 resolver,我们将要在 index.js 文件里边实现服务器。首先从 apollo-server-express 导入 Apollo-Server。我们还需要从 api/ 文件夹导入我们的 schema 和 resolvers 对象。然后,使用 Apollo Server Express 库中的 GraphQL 中间件实例化 GraphQL API。

import express from 'express';
import { ApolloServer } from 'apollo-server-express';

import typeDefs from './api/schema';
import resolvers from './api/resolvers';

const app = express();

const PORT = 4000;

const SERVER = new ApolloServer({
	typeDefs,
	resolvers
});

SERVER.applyMiddleware({ app });

app.listen(PORT, () =>
	console.log(`🚀 GraphQL playground is running at http://localhost:4000`)
);

最后,我们使用 app.listen() 来引导我们的 Express 服务器。你现在可以从终端执行命令 npm run dev 来运行服务器。服务器节点启动后,将提示成功消息,指示服务器已经启动。

现在要测试我们的 GraphQL API,在浏览器窗口中跳转 http://localhost:4000/graphql URL 并运行以下 query。

{
  allPeople {
    id
    name
    gender
    homeworld
  }
}

点击 play 按钮,你将在右侧部分看到熟悉的结果,如下所示。

一切正常,因为我们的查询类型 allPeople 具有自定义的业务逻辑,可以使用 resolver 检索所有数据(在我们的例子中,我们在 resolvers.js 中作为数据提供的模拟数据)。要获取单个人物对象,请尝试运行类似的其他 query。请记住,必须提供 ID。

{
	person(id: 1) {
		name
		homeworld
	}
}

运行上面的查询,在结果中,你可以获得得到的每个字段/属性的值以进行查询。你的结果将类似于以下内容。

完美!我相信你一定掌握了如何创建 GraphQL query 并运行它。Apollo Server 库功能很强大。它让我们能够编辑 playground。假设我们要编辑 playground 的主题?我们要做的就是在创建 ApolloServer 实例时提供一个选项,在我们的例子中是 SERVER

const SERVER = new ApolloServer({
	typeDefs,
	resolvers,
	playground: {
		settings: {
			'editor.theme': 'light'
		}
	}
});

playground 属性有很多功能,例如定义 playground 的默认端点(endpoint)以更改主题。你甚至可以在生产模式启用 playground。更多配置项可以在Apollo Server 的官方文档中找到,这里

更改主题后我们获取下面的结果。

结论

如果你一步一步完成教程,那么祝贺你! 🎉

你已经学习了如何使用 Apollo 库配置 Express 服务器来设置您自己的 GraphQL API。Apollo Server 是一个开源项目,是为全栈应用程序创建 GraphQL API 的最稳定的解决方案之一。他还支持客户端开箱即用的 React、Vue、Angular、Meteor 和 Ember 以及使用 Swift 和 Java 的 Native 移动开发。有关这方面的更多信息可以在这里找到。

在此 Github 仓库中查看教程的完整代码 👇

启动一个新的 Node.js 项目,或者寻找一个 Node 开发者?

Crowdbotics 帮助企业利用 Node 构建酷炫的东西(除此之外)。如果你有一个 Node 项目,你需要其他开发者资源,请给我们留言。Crowbotics 可以帮助您估算给定产品的功能规格的构建时间,并根据您的需要提供专门的 Node 开发者。如果你使用 Node 构建,查看 Crowdbotics

感谢 William Wickey 提供编辑方面的帮助。

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏