本文的目标是提供关于如何创建安全的 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 修改 -- 描述怎样在服务器上写入数据. 关于怎样将数据写入系统的 GraphQL 约定.
虽然本文应该展示一个关于如何构建和使用 GraphQL API 的简单但真实的场景, 但我们不会去详细介绍 GraphQL. 因为 GraphQL 团队提供了全面的文档, 并在 Introduction to GraphQL https://graphql.org/learn/ 中列出了几个最佳实践.
什么是 GraphQL 查询?
如上所述, 查询是客户端从 API 读取和操作数据的一种方式. 你可以传递对象的类型, 并选择要接收的字段类型. 下面是一个简单的查询:
- query{
- users{
- firstName,
- lastName
- }
- }
我们尝试从用户库中查询所有用户, 但只接收 firstName 和 lastName. 此查询的结果将类似于:
- {
- "data": {
- "users": [
- {
- "firstName": "Marcos",
- "lastName": "Silva"
- },
- {
- "firstName": "Paulo",
- "lastName": "Silva"
- }
- ]
- }
- }
客户端的使用非常简单.
使用 GraphQL API 的目的是什么?
创建 API 的目的是使自己的软件具有可以被其他外部服务集成的能力. 即使你的程序被单个前端程序所使用, 也可以将此前端视为外部服务, 为此, 当通过 API 为两者之间提供通信时, 你能够在不同的项目中工作.
如果你在一个大型团队中工作, 可以将其拆分为创建前端和后端团队, 从而允许他们使用相同的技术, 并使他们的工作更轻松.
在本文中, 我们将重点介绍怎样构建使用 GraphQL API 的框架.
GraphQL 比 REST 更好吗?
GraphQL 是一种适合多种情况的方法. REST 是一种体系结构方法. 如今, 有大量的文章可以解释为什么一个比另一个好, 或者为什么你应该只使用 REST 而不是 GraphQL. 另外你可以通过多种方式在内部使用 GraphQL, 并将 API 的端点维护为基于 REST 的架构.
你应该做的是了解每种方法的好处, 分析自己正在创建的解决方案, 评估你的团队使用解决方案的舒适程度, 并评估你是否能够指导你的团队快速掌握这些技术.
本文更偏重于实用指南, 而不是 GraphQL 和 REST 的主观比较. 如果你想查看这两者的详细比较, 我建议你查看我们的另一篇文章, 为什么 GraphQL 是 API 的未来.
在今天的文章中, 我们将专注于怎样用 Node.JS 创建 GraphQL API.
为什么要使用 Node.JS?
GraphQL 有好几个不同的支持库可供使用. 出于本文的目的, 我们决定使用 Node.JS 环境下的库, 因为它的应用非常广泛, 并且 Node.JS 允许开发人员使用他们熟悉的前端语法进行服务器端开发.
掌握 GraphQL
我们将为自己的 GraphQL API 设计一个构思的框架, 在开始之前, 你需要了解 Node.JS 和 Express 的基础知识. 这个 GraphQL 示例项目的源代码可以在这里找到 ( https://github.com/makinhs/node-graphql-tutorial ).
我们将会处理两种类型的资源:
Users , 处理基本的 CRUD.
Products, 我们对它的介绍会详细一点, 以展示 GraphQL 更多的功能.
Users 包含以下字段:
- id
- firstname
- lastname
- password
- permissionLevel
Products 包含以下字段:
- id
- name
- description
- price
至于编码标准, 我们将在这个项目中使用 TypeScript.
让我们开始编码!
首先, 要确保安装了最新的 Node.JS 版本. 在本文发布时, 在 Node.JS.org https://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.
接着是配置我们的 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 tutorial". 在后台场景中, 我们的配置会将 TypeScript 代码编译为纯 JavaScript, 然后在 build 文件夹中执行构建.
现在为 GraphQL API 配置一个基本框架. 为了开始我们的项目, 将添加三个基本的导入:
- Express
- Express-graphql
- Graphql-tools
把它们放在一起:
- 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 和 resolvers 中发生了什么, 它们与查询和修改的关系又是怎样的呢?
typeDefs - 我们可以从查询和修改中获得的模式的定义.
Resolvers - 在这里我们定义了查询和修改的功能和行为, 而不是想要的字段或参数.
Queries - 我们想要从服务器读取的 "获取方式".
Mutations - 我们的请求将会影响在自己的服务器上的数据.
现在让我们再次运行 NPM start, 看看我们能得到些什么. 我们希望该程序运行后产生这种效果: Graphql API 侦听 3000 端口.
我们现在可以试着通过访问 http://localhost:3000/graphql 查询和测试 GraphQL API:
好了, 现在可以编写第一个自己的查询了, 先定义为 "hello".
请注意, 我们在 typeDefs 中定义它的方式, 页面可以帮助我们构建查询.
这很好, 但我们怎样才能改变值呢? 当然是 mutation!
现在, 让我们看看当我们用 mutation 对值进行改变时会发生什么:
现在我们可以用 GraphQL Node.JS API 进行基本的 CRUD 操作了. 接下来开始使用这些代码.
Products
对于 Products, 我们将使用名为 products 的模块. 为了是本文不那么啰嗦, 我们将用内存数据库进行演示. 先定义一个模型和服务来管理 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;
- };
- }
- }
- Users
对于 users, 我们将遵循与 products 模块相同的结构. 我们将为用户提供模型和服务. 该模型将定义为:
- 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;
- };
- }
- }
提醒一下, 源代码可以在 https://github.com/makinhs/node-graphql-tutorial 找到.
现在运行并测试我们的代码. 运行 NPM start, 将在端口 3000 上运行服务器. 我们现在可以通过访问 http://localhost/ :3000/graphql 来测试自己的 GraphQL
尝试一个 mutation, 将一个项目添加到我们的 product 列表中:
为了测试它是否有效, 我们现在使用查询, 但只接收 id,name 和 price:
- query{
- products{
- id,
- name,
- price
- }
- }
将会返回:
- {
- "data": {
- "products": [
- {
- "id": 100,
- "name": "My amazing product",
- "price": 400
- }
- ]
- }
- }
很好, 按照预期工作了. 现在可以根据需要获取字段了. 你可以试着添加一些描述:
- query{
- products{
- id,
- name,
- description,
- price
- }
- }
现在我们可以对 product 进行描述. 接下来试试 user 吧.
- mutation{
- user(id:200,
- firstName:"Marcos",
- lastName:"Silva",
- password:"amaz1ingP4ss",
- permissionLevel:9,
- email:"marcos.henrique@toptal.com") {
- id
- }
- }
查询如下:
- query{
- users{
- id,
- firstName,
- lastName,
- password,
- }
- }
返回内容如下:
- {
- "data": {
- "users": [
- {
- "id": 200,
- "firstName": "Marcos",
- "lastName": "Silva",
- "password": "kpj6Mq0tGChGbZ+BT9Nw6RMCLReZEPPyBCaUS3X23lZwCCp1Ogb94/
- oqJlya0xOBdgEbUwqRSuZRjZGhCzLdeQ==",
- "email": "marcos.henrique@toptal.com"
- }
- ]
- }
- }
到此为止, 我们的 GraphQL 骨架完成! 虽然离实现一个有用的, 功能齐全的 API 还需要很多步骤, 但现在已经设置好了基本的核心功能.
总结和最后的想法
让我们回顾一下本文的内容:
在 Node.JS 下可以通过 Express 和 GraphQL 库来构建 GraphQL API;
基本的 GraphQL 使用;
查询和修改的基本用法;
为项目创建模块的基本方法;
测试我们的 GraphQL API;
为了集中精力关注 GraphQL API 本身, 我们忽略了几个重要的步骤, 可简要总结如下:
新项目的验证;
使用通用的错误服务正确处理异常;
验证用户可以在每个请求中使用的字段;
添加 JWT 拦截器以保护 API;
使用更有效的方法处理密码哈希;
添加单元和集成测试;
请记住, 我们在 Git (https://github.com/makinhs/node-graphql-tutorial) 上有完整的源代码. 可以随意使用, fork, 提问, pull 并运行它! 请注意, 本文中提出的所有标准和建议并不是一成不变的.
这只是设计 GraphQL API 的众多方法之一. 此外, 请务必更详细地阅读和探索 GraphQL 文档, 以了解它提供的内容以及怎样使你的 API 更好.
来源: http://www.css88.com/web/node-js/15228.html