用Node.js建立安全的 GraphQL API

翻译:疯狂的技术宅
https://www.toptal.com/graphq...

本文首发微信公众号:前端先锋
欢迎关注,天天都给你推送新鲜的前端技术文章前端


本文的目标是提供关于如何建立安全的 Node.js GraphQL API 的快速指南。node

你可能会想到一些问题:git

  • 使用 GraphQL API 的目的是什么?
  • 什么是GraphQL API?
  • 什么是GraphQL查询?
  • GraphQL的好处是什么?
  • GraphQL是否优于REST?
  • 为何咱们使用Node.js?

这些问题都是有意义的,但在回答以前,咱们应该深刻了解当前 Web 开发的状态:程序员

  • 如今几乎全部的解决方案都使用了某种应用程序编程接口(API)。
  • 即便你只用社交网络(如Facebook或Instagram),仍然会用到使用API​​的前端。
  • 若是你感到好奇,你会发现几乎全部在线娱乐服务都在用不一样类型的API,包括Netflix,Spotify和YouTube等。

你会发现几乎在每种状况下都会有一个不须要你去详细了解的API,例如你不须要知道它们是怎样构建的,而且不须要使用与他们相同的技术就可以将其集成到你本身的系统中。API容许你提供一种能够在服务器和客户端通讯之间进行通用标准通讯的方式,而没必要依赖于特定的技术栈。github

经过结构良好的API,能够拥有可靠、可维护且可扩展的API,能够为多种客户端和前端应用提供服务。面试

什么是 GraphQL API?

GraphQL 是一种 API 所使用的查询语言,由Facebook开发并用于其内部项目,并于2015年公开发布。它支持读取、写入和实时更新等操做。同时它也是开源的,一般会与REST和其余架构放在一块儿进行比较。简而言之,它基于:typescript

  • GraphQL查询 —— 容许客户端进行读取和控制接收数据的方式。
  • GraphQL 修改 —— 描述怎样在服务器上写入数据。关于怎样将数据写入系统的GraphQL约定。

虽然本文应该展现一个关于如何构建和使用GraphQL API的简单但真实的场景,但咱们不会去详细介绍GraphQL。由于GraphQL团队提供了全面的文档,并在Introduction to GraphQL中列出了几个最佳实践。数据库

什么是GraphQL查询?

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

query{
  users{
    firstName,
    lastName
  }
}

咱们尝试从用户库中查询全部用户,但只接收firstNamelastName。此查询的结果将相似于:npm

{
  "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:

clipboard.png

好了,如今能够编写第一个本身的查询了,先定义为“hello”。

clipboard.png

请注意,咱们在typeDefs中定义它的方式,页面能够帮助咱们构建查询。

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

如今,让咱们看看当咱们用mutation对值进行改变时会发生什么:

clipboard.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列表中:

clipboard.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/no...)上有完整的源代码。能够随意使用、fork、提问、pull 并运行它!请注意,本文中提出的全部标准和建议并非一成不变的。

这只是设计GraphQL API的众多方法之一。此外,请务必更详细地阅读和探索GraphQL文档,以了解它提供的内容以及怎样使你的API更好。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章


欢迎继续阅读本专栏其它高赞文章:

相关文章
相关标签/搜索