如何创建安全的 Node.js GraphQL API

已发表: 2022-03-11

在本文中,我们旨在提供有关如何创建安全的 Node.js GraphQL API 的快速指南。

可能会想到的一些问题可能是:

  • 使用 GraphQL API 的目的是什么?
  • 什么是 GraphQL API?
  • 什么是 GraphQL 查询?
  • GraphQL 有什么好处?
  • GraphQL 比 REST 更好吗?
  • 为什么我们使用 Node.js?

所有这些都是有效的问题,但在回答它们之前,我们应该深入了解一下 Web 开发的当前状态:

  • 您今天会发现的几乎所有解决方案都使用某种应用程序编程接口 (API)。
  • 即使您只是使用社交网络,例如 Facebook 或 Instagram,您仍然连接到使用 API 的前端。
  • 如果你好奇,你会发现几乎所有在线娱乐服务都使用不同类型的 API,包括 Netflix、Spotify 和 YouTube 等服务。

几乎在每个场景中,您都会发现不需要详细了解的 API,例如,您不需要知道它们是如何构建的,也不需要使用它们过去使用的相同技术能够将其集成到您自己的系统中。 所提供的 API 允许您提供一种以通用标准在服务之间进行通信的方式,服务和客户端都可以在无需依赖特定技术堆栈的情况下进行通信。

有了结构良好的 API,就有可能拥有一个可靠的、可维护的和可扩展的 API,它可以服务于多种客户端和前端应用程序。

也就是说,什么GraphQL API?

GraphQL 是一种 API 查询语言,为 Facebook 内部使用而开发,并于 2015 年发布供公众使用。它支持读取、写入和实时更新。 它也是开源的,通常与 REST 和其他架构进行比较。 简而言之,它基于:

  • GraphQL 查询- 这允许客户端读取和操作应如何接收数据。
  • GraphQL Mutations - 这是在服务器上写入数据的方法。 它是关于如何将数据写入系统的 GraphQL 约定。

尽管本文旨在展示一个关于如何构建和使用 GraphQL API 的简单但真实的场景,但我们不会详细介绍 GraphQL。 原因很简单,因为 GraphQL 团队提供了全面的文档,并在他们的 GraphQL 简介中列出了几个最佳实践。

什么是 GraphQL 查询?

如前所述,查询是客户端可以从 API 读取和操作数据的方式。 您可以传递对象的类型并选择要接收的字段类型。 一个简单的查询如下所示:

 query{ users{ firstName, lastName } }

在这个查询中,我们试图从用户的模式中访问所有用户,但只接收firstNamelastName 。 此查询的结果将类似于,例如:

 { "data": { "users": [ { "firstName": "Marcos", "lastName": "Silva" }, { "firstName": "Paulo", "lastName": "Silva" } ] } }

客户端使用非常简单。

使用 GraphQL API 的目的是什么?

创建 API 的目的是使软件能够被其他外部服务集成为服务。 即使您的应用程序由单个前端使用,您也可以将此前端视为外部服务,为此,当通过 API 提供两者之间的通信时,您将能够在不同的项目中工作。

如果您在一个大型团队中工作,则可以将其拆分为创建前端和后端团队,从而使两者可以使用相同的技术并使工作更轻松。 在构建 API 时,重要的是选择更适合项目的方式以及使您更接近所需解决方案的方式。

在本文中,我们将专注于构建使用 GraphQL 的 API 的框架。

GraphQL 比 REST 更好吗?

这可能有点逃避现实,但我情不自禁:这取决于.

GraphQL 是一种非常适合多种场景的方法。 REST 是一种架构方法,在多个场景中也得到了证明。 如今,有大量文章解释了为什么一个比另一个更好,或者为什么你应该只使用 REST 而不是 GraphQL。 此外,您可以通过多种方式在内部使用 GraphQL,并且仍然将 API 的端点维护为基于 REST 的架构。

最好的指导是了解每种方法的好处,分析您正在创建的解决方案,评估您的团队使用该解决方案的舒适程度,并评估您是否能够指导您的团队学习和掌握在选择方法之前快速加快速度。

本文更多的是实用指南,而不是 GraphQL 和 REST 的主观比较。 如果您想阅读两者的详细比较,我建议您查看我们的另一篇文章,GraphQL vs. REST - A GraphQL Tutorial。

在今天的文章中,我们将重点介绍使用 Node.js 创建 GraphQL API。

为什么我们使用 Node.js?

GraphQL 有几个不同的库可供您使用。 出于本文的目的,我们决定将 JavaScript 与 Node.js 一起使用,因为 Node.js 的广泛使用以及 Node.js 允许开发人员使用熟悉的前端语法进行服务器端开发这一事实。

将我们的方法与基于 REST 的 API 进行比较也很有用,类似于在另一篇 Toptal 工程博客文章中演示的方法:在 Node.js 中创建安全的 REST API。 本文还展示了使用 Node.js 和 Express 来开发一个骨架 REST API,这将允许您比较这两种方法之间的一些差异。 Node.js 还设计有可扩展的网络应用程序、一个全球社区和几个您可以在 npm 网站上找到的开源库。

这一次,我们将展示如何使用 GraphQL、Node.js 和 Express 构建框架 API!

上手 GraphQL 教程

如前所述,我们将为 GraphQL API 构建一个框架概念,在继续之前您需要了解 Node.js 和 Express 的基础知识。 为这个 GraphQL 示例制作的项目的源代码可在此处获得。

我们将处理两种类型的资源:

  • 用户,我们将为其处理基本的 CRUD。
  • 产品,我们将有一些细节来展示更多 GraphQL 的力量。

用户将包含以下结构:

  • ID
  • 电子邮件
  • 密码
  • 权限级别

产品将包含以下结构:

  • ID
  • 姓名
  • 描述
  • 价钱

至于编码标准,我们将在这个项目中使用 TypeScript。 在源文件中,您将能够配置所有内容以开始使用 TypeScript 进行编码。

让我们编码吧!

首先,确保您安装了最新的 Node.js 版本。 根据 Nodejs.org,在发布时,当前版本是 10.15.3。

初始化项目

让我们从一个可以命名为node-graphql的新文件夹开始。 在那里,我们可以打开终端或 Git CLI 控制台并使用以下命令启动魔法: npm init

配置我们的依赖和 TypeScript

为了加快这个过程,在我们的 Git 存储库中用以下内容替换你的package.json应该包含所有必要的依赖项:

 { "name": "node-graphql", "version": "1.0.0", "description": "", "main": "dist/index.js", "scripts": { "tsc": "tsc", "start": "npm run tsc && node ./build/app.js" }, "author": "", "license": "ISC", "dependencies": { "@types/express": "^4.16.1", "@types/express-graphql": "^0.6.2", "@types/graphql": "^14.0.7", "express": "^4.16.4", "express-graphql": "^0.7.1", "graphql": "^14.1.1", "graphql-tools": "^4.0.4" }, "devDependencies": { "tslint": "^5.14.0", "typescript": "^3.3.4000" } }

使用更新后的package.json ,再次点击终端并使用: npm install 。 它将安装在 Node.js 和 Express 中运行此 GraphQL API 所需的所有依赖项。

下一部分是配置我们的 TypeScript 模式。 我们在根文件夹中需要一个名为tsconfig.json的文件,其中包含以下内容:

 { "compilerOptions": { "target": "ES2016", "module": "commonjs", "outDir": "./build", "strict": true, "esModuleInterop": true } }

此配置的代码逻辑将出现在 app 文件夹中。 在那里我们可以创建一个app.ts文件,并在其中添加以下代码进行基本测试:

 console.log('Hello Graphql Node API tutorial');

通过我们的配置,我们现在可以运行npm start并等待构建并能够测试一切是否正常工作。 在您的终端控制台中,您应该会看到我们的“Hello GraphQL Node API 教程”。 在后面的场景中,配置基本上是将 TypeScript 代码编译成纯 JavaScript,然后在build文件夹中执行我们的构建。

现在让我们为我们的 GraphQL API 配置一个基本框架。 为了开始我们的项目,我们将添加三个基本导入:

  • 表示
  • Express-graphql
  • Graphql 工具

让我们开始把它们放在一起:

 import express from 'express'; import graphqlHTTP from 'express-graphql'; import {makeExecutableSchema} from 'graphql-tools';

现在我们应该可以开始编写代码了。 下一步是在 Express 中处理我们的应用程序和基本的 GraphQL 配置,例如:

 import express from 'express'; import graphqlHTTP from 'express-graphql'; import {makeExecutableSchema} from 'graphql-tools'; const app: express.Application = express(); const port = 3000; let typeDefs: any = [` type Query { hello: String } type Mutation { hello(message: String) : String } `]; let helloMessage: String = 'World!'; let resolvers = { Query: { hello: () => helloMessage }, Mutation: { hello: (_: any, helloData: any) => { helloMessage = helloData.message; return helloMessage; } } }; app.use( '/graphql', graphqlHTTP({ schema: makeExecutableSchema({typeDefs, resolvers}), graphiql: true }) ); app.listen(port, () => console.log(`Node Graphql API listening on port ${port}!`));

我们正在做的是:

  • 为我们的 Express 服务器应用启用端口 3000。
  • 定义我们想要使用的查询和突变作为一个快速示例。
  • 定义查询和突变如何工作。

好的,但是 typeDefs 和解析器发生了什么,以及与查询和突变的关系?

  • typeDefs - 我们可以从查询和突变中获得的模式的定义。
  • 解析器- 在这里我们定义查询和突变应该如何工作的功能和行为,而不是字段或必需参数的期望。
  • 查询- 我们要从服务器读取的“获取”。
  • 突变- 我们的请求将影响我们自己服务器上的任何数据。

现在,让我们再次运行npm start看看我们有什么。 我们希望应用程序运行时会显示以下消息:Node Graphql API 在端口 3000 上侦听!

我们现在可以尝试通过以下方式在我们自己的服务器上查询和测试 GraphQL API:http://localhost:3000/graphql

GraphQL 教程:服务器测试

太好了,现在我们可以编写我们自己的第一个查询,它被定义为“hello”。

GraphQL 教程:第一个查询

请注意,我们在typeDefs中定义它的方式,页面可以帮助我们构建查询。

这很好,但我们如何改变价值呢? 突变!

现在,让我们看看当我们使用突变更改内存中的值时会发生什么:

GraphQL 教程:突变演示

现在我们可以使用我们的 GraphQL Node.js API 进行基本的 CRUD 操作。 现在让我们推进我们的代码。

产品

对于产品,我们将使用一个名为 products 的模块。 为了简化本文,我们将使用内存数据库进行演示。 我们将定义一个模型和一个服务来管理产品。

我们的模型将基于以下内容:

 export class Product { private id: Number = 0; private name: String = ''; private description: String = ''; private price: Number = 0; constructor(productId: Number, productName: String, productDescription: String, price: Number) { this.id = productId; this.name = productName; this.description = productDescription; this.price = price; } }

将与 GraphQL 通信的服务将定义为:

 export class ProductsService { public products: any = []; configTypeDefs() { let typeDefs = ` type Product { name: String, description: String, id: Int, price: Int } `; typeDefs += ` extend type Query { products: [Product] } `; typeDefs += ` extend type Mutation { product(name:String, id:Int, description: String, price: Int): Product! }`; return typeDefs; } configResolvers(resolvers: any) { resolvers.Query.products = () => { return this.products; }; resolvers.Mutation.product = (_: any, product: any) => { this.products.push(product); return product; }; } }

用户

对于用户,我们将遵循与产品模块相同的结构。 我们将为用户提供模型和服务。 该模型将定义为:

 export class User { private id: Number = 0; private firstName: String = ''; private lastName: String = ''; private email: String = ''; private password: String = ''; private permissionLevel: Number = 1; constructor(id: Number, firstName: String, lastName: String, email: String, password: String, permissionLevel: Number) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.email = email; this.password = password; this.permissionLevel = permissionLevel; } }

同时,我们的服务将是:

 const crypto = require('crypto'); export class UsersService { public users: any = []; configTypeDefs() { let typeDefs = ` type User { firstName: String, lastName: String, id: Int, password: String, permissionLevel: Int, email: String } `; typeDefs += ` extend type Query { users: [User] } `; typeDefs += ` extend type Mutation { user(firstName:String, lastName: String, password: String, permissionLevel: Int, email: String, id:Int): User! }`; return typeDefs; } configResolvers(resolvers: any) { resolvers.Query.users = () => { return this.users; }; resolvers.Mutation.user = (_: any, user: any) => { let salt = crypto.randomBytes(16).toString('base64'); let hash = crypto.createHmac('sha512', salt).update(user.password).digest("base64"); user.password = hash; this.users.push(user); return user; }; } }

提醒一下,可以从此链接使用源代码。

现在我们可以播放和测试我们的代码了。 让我们运行npm start 。 我们将在端口 3000 上运行服务器。我们现在可以在 http://localhost:3000/graphql 访问 GraphQL 进行测试。

让我们尝试一个突变来将一个项目添加到我们的产品列表中:

Node.js GraphQL 变异演示

为了测试它是否有效,我们现在将使用产品查询,但只接收idnameprice

 query{ products{ id, name, price } } The response will be: { "data": { "products": [ { "id": 100, "name": "My amazing product", "price": 400 } ] } }

就是这样; 产品按预期工作。 现在我们可以根据需要播放和切换字段。 您可以尝试添加描述:

 query{ products{ id, name, description, price } }

现在我们可以得到我们产品的描述。 现在让我们试试用户。

 mutation{ user(id:200, firstName:"Marcos", lastName:"Silva", password:"amaz1ingP4ss", permissionLevel:9, email:"[email protected]") { id } }

查询将类似于:

 query{ users{ id, firstName, lastName, password, email } }

回复如下:

 { "data": { "users": [ { "id": 200, "firstName": "Marcos", "lastName": "Silva", "password": "kpj6Mq0tGChGbZ+BT9Nw6RMCLReZEPPyBCaUS3X23lZwCCp1Ogb94/oqJlya0xOBdgEbUwqRSuZRjZGhCzLdeQ==", "email": "[email protected]" } ] } }

现在我们的 GraphQL 骨架已经准备好了! 从这里到一个有用的、功能齐全的 API 有很多步骤,但现在已经设置了基本的核心。

总结和最终想法

即使是缩短的前沿,这篇文章也相当大,包含很多关于开发 GraphQL Node.js API 的基本信息。

让我们回顾一下到目前为止我们所涵盖的内容:

  • 使用 Node.js 与 Express 和 GraphQL 构建 GraphQL API;
  • 基本的 GraphQL 使用;
  • 查询和突变的基本用法;
  • 为您的项目创建模块的基本方法;
  • 测试我们的 GraphQL API;

为了更专注于事物的开发方面,我们避免了几个重要的项目,可以简要总结如下:

  • 新项目的验证;
  • 使用通用错误服务正确处理错误;
  • 通过通用服务验证用户可以在每个请求中使用的字段;
  • 添加 JWT 拦截器以保护 API;
  • 用更有效的方法处理密码哈希;
  • 添加单元和集成测试;

请记住,我们在这个 Git 链接上有完整的源代码。 随意使用、分叉、打开问题、提出拉取请求并使用它! 请注意,本文中提出的所有标准和建议都不是一成不变的。

这只是可用于开始设计您自己的 GraphQL API 的众多方法之一。 此外,请务必更详细地阅读和探索 GraphQL,了解它提供的功能以及它如何使您的 API 变得更好。