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

本文的目标是提供关于如何创建安全的 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中列出了几个最佳实践。

什么是GraphQL查询?

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

query{
  users{
    firstName,
    lastName
  }
}

我们尝试从用户库中查询所有用户,但只接收firstNamelastName。此查询的结果将类似于:

{
  "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/no...)。

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

  • Users ,处理基本的CRUD。
  • Products, 我们对它的介绍会详细一点,以展示GraphQL更多的功能。

Users 包含以下字段:

  • id
  • firstname
  • lastname
  • email
  • password
  • permissionLevel

Products 包含以下字段:

  • id
  • name
  • description
  • price

至于编码标准,我们将在这个项目中使用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

接着是配置我们的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:

1.png

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

2.png

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

这很好,但我们怎样才能改变值呢?当然是mutation!

现在,让我们看看当我们用mutation对值进行改变时会发生什么:

3.png

现在我们可以用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/no... 找到。

现在运行并测试我们的代码。运行npm start,将在端口3000上运行服务器。我们现在可以通过访问http://localhost:3000/graphql来测试自己的GraphQL

尝试一个mutation,将一个项目添加到我们的product列表中:

4.png

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

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,
    email
  }
}

返回内容如下:

{
"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更好。

英文地址原文:https://www.toptal.com/graphql/graphql-nodejs-api

Image placeholder
前端答疑
未设置
  74人点赞

没有讨论,发表一下自己的看法吧

推荐文章
你不知道的 Graphql

一起入门Graphql[toc]介绍GraphQL既是一种用于API的查询语言也是一个满足你数据查询的运行时。如何理解呢?GraphQL作为通用的REST架构的替代方案而被开发出来,通俗的讲,在架构中

医疗行业数据安全的主要风险和应对分析

作者:柳遵梁依据Verizon数据泄露报告中对客观现状、数据分布和数据流动等方面综合分析,医疗行业数据安全的主要风险包括如下几个方面:  一、人的安全风险和对策  1.人的安全风险是医疗数据安全的最大

和大型企业相比,小企业可能并不是安全的薄弱环节

拥有250名或更少员工的企业通常比规模较大的企业使用更高比例的安全从业人员。小型企业经常因为成为大型企业供应链攻击的门户而声名狼藉。但从一份关于小型企业安全的报告中可以看出,情况可能并非如此。作为(I

Fortinet的云安全观:上云≠安全 云安全市场或迎“又一春”!

近年来网络攻击事件频繁发生,企业对于网络安全的关注度已经到达前所未有的高度,如何保证业务的正常运转是每个企业最为关注的问题之一。而随着越来越多的企业将业务扩展到云端,云上安全问题也成为企业必谈的话题!

打好网络安全攻坚战 思科年度安全报告系列关注未知安全挑战

当网络攻击愈演愈烈,当勒索软件愈加嚣张,企业对于网络安全的重视程度稳步提升。网络安全在企业中的地位从没像今天这般高涨。但不可否认的是,即便企业提升了对网络安全的防护,但网络安全威胁依旧长期存在且愈加复

nodejs为什么叫node?

nodejs为什么叫node?最开始NodeJs作者Dahl取的名字叫web.js,仅仅是一个web服务器,是Apache和其他“阻塞”服务器的替代方案。但是该项目很快就超出了他最初的Web服务器库的

[PHP 安全] OWASP 维护的 PHP 安全配置速查表

介绍 这个页面的目的是为了帮助那些配置PHP和运行它的web服务器的人确保它的安全性。 下面你将找到有关php.ini文件的正确配置信息。 php.ini 下面的一些设置需要适应你的系统,特别是se

PHP 安全问题入门:10 个常见安全问题 + 实例讲解

相对于其他几种语言来说,PHP在web建站方面有更大的优势,即使是新手,也能很容易搭建一个网站出来。但这种优势也容易带来一些负面影响,因为很多的PHP教程没有涉及到安全方面的知识。 此帖子分为几部分

梆梆安全:做以结果为导向的安全服务商

作为国内领先的安全服务提供商,梆梆安全不谈概念,始终从基本出发,致力于解决客户的根本性问题。通过运用领先技术提供专业可靠的服务,为全球政府、企业、开发者和消费者打造安全、稳固、可信的安全生态环境,其用

云架构远没想象般安全 派拓网络五大建议助力云安全

当企业业务大量向云端转移,云上安全问题变得愈加严峻,如何保障云端业务的安全成为企业关注的重点问题之一。前不久,网络安全企业PaloAltoNetworks(派拓网络)发布了一份云安全报告,揭示亚太区大

周鸿祎:360回归企业安全 携手安全生态打好网络攻坚战

8月19日,主题为“应对网络战、共建大生态、同筑大安全”的第七届互联网安全大会在北京雁栖湖国际会展中心隆重开幕,来自国内外的百余位专家共同讨论全球网络安全的最新形势,从战略、产业和技术等多个层面,探讨

学习 nodejs+mongodb+koa2 写接口(一) 环境布置

一.环境准备最近在学用Nodejs写后端接口,了解到koa2是Nodejs的一个框架。可以快速开发后端接口,同时也能更快熟悉Nodejs以下是所需的环境node  v7.6+,可以用nvm或者n安装指

学习 nodejs+mongodb+koa2 写接口(二) koa2教程入门

一.hellokoa安装koa2#初始化package.json npminit #安装koa2 npminstallkoahelloworld代码constKoa=require('koa') c

如何查看nodejs版本号?

在windows上查看nodejs的版本号想要在电脑上查node版本号,可以打开cmd命令行,使用命令来查看。1、在开始菜单的搜索框中输入cmd,点击cmd.exe打开cmd命令窗口。2、使用命令查看

为什么SQL正在击败NoSQL,这对未来的数据意味着什么

导读:经过多年的沉寂之后,今天的SQL正在复出。缘由如何?这对数据社区有什么影响?看看本文的分析。以下为译文。自从可以利用计算机做事以来,我们一直在收集的数据以指数级的速度在增长,因此对于数据存储、处

Linux 安装 Node.js

因为我用最简单yum命令安装node.js发现node的版本不是最新的所以自己手动安装了nodejs流程如下 进入服务器安装nodejs安装包 进入保存包的目录 $cddownload 下载二进制

使用 Node.js 以来的感想与总结

使用PHP时期 在使用php的时候,曾经用的最多的框架就是ThinkPHP,后来尝试体验过LaravelorYii,反正不管用啥框架,PHP的那一堆环境是必不可少,虽然现在有很多集成环境或各种各样的

Linux 安装 Node.js

因为我用最简单yum命令安装nodejs发现node的版本不是最新的所以自己手动安装了nodejs流程如下 进入服务器安装nodejs安装包 进入保存包的目录 $cddownload 下载二进制包

Node.js 到底是什么?

Node.js是一个JavaScript的运行环境,听起来很棒,然而是什么意思呢?它又是怎么使用的呢? Node运行环境涵盖所有你运行用JavaScript编写的项目所需要的东西。如果你知道Java的

怎么查看nodejs是否安装?

查看nodejs是否安装的方法:1、cmd执行命令开始菜单搜索框输入cmd,点击第一个结果,打开cmd命令行。在cmd命令行中,输入node-v并执行,显示node版本,证明安装成功。例如我的版本是1

nodejs都能做什么?

Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。Node是一个基于ChromeJavaScript运行时

node.js有什么用?

Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。Node是一个基于ChromeJavaScript运行时

node.js什么意思?

Node对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境下运行得更好。V8引擎执行Javascript的速度非常快,性能非常好。Node是一个基于ChromeJavaScript运行时

nodejs怎么解析less?

nodejs怎么解析less?在cmd中使用npm中的less模块来解析less。npm(全称NodePackageManager,即“node包管理器”)是以JavaScript编写的软件包管理系统

nodejs怎么运行项目?

nodejs怎么运行项目?想要运行nodeJS项目,只需要安装好node的环境就可以了,不需要其他的配置。使用cmd命令,打开window窗口,输入node-v,如果运行的结果是nodeJS的一个版本