(一) 《Nest.js:渐进式node.js框架》 介绍

2,446 阅读11分钟

Nestjs 是一个构建在Node.js Express服务器之上的现代Web框架。利用现代ES6 JavaScript提供的强大灵活性和TypeScript在编译期间强制实现类型安全,将可扩展的Node.js服务器提升到一个全新的水平。Nest将三种不同的技术结合为一个成功的组合,使应用程序高度可测试,可扩展,松散耦合和可维护:

  • 面向对象编程(OOP):围绕对象而不是行为和可重用性构建的模型。
  • 函数编程(FP):设计的函数不依赖于全局状态,即对于一些不改变的设置参数,函数f(x)每次都返回相同的结果。
  • 函数式响应型编程(FRP):扩展上面的FP和响应型编程。函数式响应型编程是其核心功能编程,它考虑了跨时间的流程。它在UI,模拟,机器人和其他应用程序中非常有用,其中特定时间段的结果可能与另一个时间段的结果不同。

主题讨论

1. Nest CLI

Nest的第5版中新增了一个CLI命令,允许通过命令行生成项目和文件。 全局安装CLI:

npm install -g @nestjs/cli

或者通过Docker:

docker pull nestjs/cli:[version]

可以使用以下命令生成新的Nest项目:

nest new [project-name]

此过程将询问 name, description, version (defaults to 0.0.0), and author (你的名字)。 完成此过程后,您将拥有一个设置好的Nest项目,其中依赖项安装在 node_modules 文件夹。 new 命令还会询问你想用什么包管理器,以同样的方式,有 yarn 或 npm 可以用。

CLI中最常用的命令是 generate (g) 命令, 允许你创建新的 controllers, modules, servies 或Nest支持的任何其它组件:

  1. class (cl)
  2. controller (co)
  3. decorator (d)
  4. exception (e)
  5. filter (f)
  6. gateway (ga)
  7. guard (gu)
  8. interceptor (i)
  9. middleware (mi)
  10. module (mo)
  11. pipe (pi)
  12. provider (pr)
  13. service (s)

括号中的字符串是该特定命令的别名

nest generate service [service-name]

在控制台中,您可以输入:

nest g s [service-name]

最后,Nest CLI提供 info (i) 命令以显示项目的有关信息。这个命令将输出如下信息:

[System Information]
OS Version     : macOS High Sierra
NodeJS Version : v8.9.0
YARN Version    : 1.5.1
[Nest Information]
microservices version : 5.0.0
websockets version    : 5.0.0
testing version       : 5.0.0
common version        : 5.0.0
core version          : 5.0.0

2.依赖注入

依赖注入是一种技术,它提供一个依赖对象,如一个模块或者一个组件,然后依赖的对象就像服务一样,可以把它注入到组件的构造函数中。 举个例子,在这里我们将 UserRespository 服务注入到 UserService 的构造函数中, 从而提供从 UserService 组件内部访问User Database存储库的权限。

@Injectable()
export class UserService implements IUserService {
    constructor(@Inject('UserRepository') private readonly UserRepository: typeof User) { }
    ...
}

然后,这个 UsersService 将被注入到 UsersController,将提供控制器的路由对 UsersService 访问。后续章节中会介绍路由和依赖注入的更多信息。

3.认证

身份验证是开发过程中最重要的方面之一。 作为开发人员,我们始终希望确保用户只能访问他们有权访问的资源。 近年来,身份验证方法已经扩展到变得更加复杂,但我们仍然需要相同的服务器端逻辑,以确保这些经过身份验证的用户始终是合法用户,并将这种身份验证持久化,这样就不需要为每一个 REST API 或 Websocket 的调用者重新进行身份验证,因为那将提供非常糟糕的用户体验。

Nest 选择集成Node.js生态系统中非常有名的认证库 Passport,同时使用JWT(JSON Web Token)策略。 Passport是在调用控制器上的端点之前传递HTTP调用的中间件。下面是为示例项目编写的 AuthenticationMiddleware ,它扩展了 NestMiddleware,根据请求有效负载中的电子邮件对每个用户进行身份验证。

@Injectable()  
export class AuthenticationMiddleware implements NestMiddleware {  
   constructor(private userService: UserService) { }  

   async resolve(strategy: string): Promise<ExpressMiddleware> {  
       return async (req, res, next) => {  
           return passport.authenticate(strategy, async (/*...*/args: any[]) => {  
               const [, payload, err] = args;  
                if (err) {  
                    return res.status(HttpStatus.BAD_REQUEST).send('Unable to authenticate the user.');  
                }  

               const user = await this.userService.findOne({
                    where: { email: payload.email }
               });  
                req.user = user;  
                return next();  
            })(req, res, next);  
        };  
    }  
}

Nest还实现了Guards,与其他提供程序一样,使用@Injectable()装饰器进行装饰。 Guards 可以控制用户可以访问的内容端点。 将在后续“身份验证”章节中更详细地讨论 Guards。

4.ORM

ORM是服务器和数据库之间通信时最重要的概念之一:对象关系映射。 ORM提供内存中对象(诸如定义的 User 或 Comment 类) 和数据库中的关系表之间的映射。 允许您创建一个数据传输对象,该对象知道如何将存储在内存中的对象写入数据库,并将结果从SQL或其他查询语言读回内存。在这,我们将讨论三种不同的ORM: 两种关系型 orm 和一种针对 NoSQL 数据库的 orm。

TypeORM是Node.js中最成熟和最流行的ORM之一, Nest为其提供自己的包: @nestjs/typeorm,TypeORM非常强大,并且支持许多数据库,如MySQL,PostgreSQL,MariaDB,SQLite,MS SQL Server,Oracle和WebSQL。同时,Sequelize也是关系数据库的另一个ORM。

如果TypeORM是最流行的ORM之一,那么Sequelize则是Node.js世界中最受欢迎的ORM之一。 它是用纯JavaScript编写的, 但是通过 sequelize-typescript 和 @types/sequelize 包进行了 TypeScript 绑定。Sequelize拥有强大的事务支持,关系,读取复制和更多功能。

最后一个ORM是处理非关系数据库或NoSQL数据库的ORM。mongoose 包处理MongoDB和JavaScript之间的对象关系。 与关系数据库相比,两者之间的实际映射关系要密切得多,因为MongoDB以JSON格式存储其数据,JSON格式代表JavaScript Object Notation。 Mongoose也有 @nestjs/mongoose ,并提供通过查询链查询数据库的能力。

5.REST API

REST是创建API的主要设计范例之一。它代表代表状态转移传输,并使用JSON作为传输格式,这与Nest存储对象的方式一致,因此它适合使用和返回 HTTP 调用。

客户端对服务器进行HTTP请求,服务器将根据URL和HTTP动词将调用路由到正确的Controller,可选择在到达Controller之前将其传递给一个或多个中间件。 然后,Controller将其交给service进行处理,其中可能包括通过ORM与数据库进行通信。

如果客户端请求资源(GET请求),则返回一个可选主体; 如果是 POST/PUT/DELETE,则返回一个200 / 201 HTTP OK,如果没有响应主体。

6.WebSockets

Websocket 是从服务器连接和发送 / 接收数据的另一种方式。

使用 websocket,客户端连接到服务器,然后订阅特定的通道。 然后,客户端可以将数据推送到订阅的通道。 服务器将接收此数据,然后将其广播到订阅该特定通道的每个客户端。 这使得多个客户端都可以接收实时更新,而无需手动进行 API 调用。 大多数聊天应用程序使用WebSockets进行实时通信,一旦其中一个成员发送消息,组消息中的每个人都将收到消息。 与传统的请求-响应 API 相比,Websockets 允许更多的流式数据传输方法,因为 Websockets 可以在接收到数据时广播数据。

7.微服务

微服务允许将Nest将应用程序结构化为松散耦合服务的集合。 在Nest中,微服务略有不同,因为它们是一个使用不同于 HTTP 的传输层的应用程序。该层可以是TCP或Redis pub / sub等。

Nest支持TCP和Redis,但如果您与另一个传输层结合,则可以使用 CustomTransportStrategy 接口实现它。 微服务很棒,因为它们允许团队在全局项目中处理自己的服务,并且在不影响项目其余部分的情况下对服务进行更改,因为它是松散耦合的。 这使得持续交付和持续集成独立于其他团队的微服务。

8.GraphQL

正如我们在上面看到的,REST 是设计 api 时的一种范例, 但是有一种新的方式来考虑创建和使用 api:GraphQL。

使用GraphQL,URL将接受带有JSON对象的查询参数,而不是每个资源都有自己的URL指向。 此JSON对象定义要返回的数据的类型和格式。 Nest通过 @nestjs/graphql 包提供了这方面的功能。

这将包括项目中GraphQLModule, 它是Apollo服务器的包装器。

9.路由

在讨论Web框架时,路由是核心之一。当客户端需要访问服务器的端点时, 这些端点中的每一个都描述了如何检索/创建/操作存储在服务器上的数据。 描述API端点的每个Component必须具有@Controller() 装饰器,用于描述该组件的端点集的 API 前缀。

@Controller('hello')
export class HelloWorldController {
  @Get(‘world’)
  printHelloWorld() {
    return ‘Hello World’;
  }
}

10.Nestg工具装饰器

@Module:项目中此可重用代码包的定义,它接受以下参数来定义其行为。
  ⋅⋅Imports:包含此模块中使用的组件。
  ⋅⋅Exports:导出模块。
  ⋅⋅Components:通过 Nest 注入的组件在这个模块上共享。
  ⋅⋅Controllers:在此模块中创建的控制器,这些控制器将根据定义的路由定义API端点。
@Injectable:Nest中几乎所有东西都是可以通过构造函数注入的提供者。提供者用@Injectable()装饰。
  ..Middleware:在将请求传递给路由处理程序之前运行的函数。
  ..Interceptor:类似于中间件,它们在执行方法之前和之后绑定额外的逻辑,它们可以转换或完全覆盖函数。拦截器的灵感来自面向方面编程(AOP)。
  ..Pipe:与拦截器功能的一部分类似,管道将输入数据转换为所需的输出。
  ..Guard:一个更聪明,更小众的中间件,Guards的唯一目的是确定路由器处理程序是否应该处理请求。
  ..* Catch:告诉ExceptionFilter要查找的异常,然后将数据绑定到它。
@Catch:将元数据绑定到异常过滤器,并告诉Nest过滤器只查找@Catch中列出的异常。

11.OpenAPI (Swagger)

在编写 Nest 服务器时,api文档是非常重要的,否则使用该 API 开发客户端的开发人员不知道发送什么或将得到什么。

最流行的文档引擎之一是 Swagger。 像其他模块一样,Nest 为 OpenAPI (Swagger) spec,提供了一个专用模块 @nestjs/swagger。

这个模块提供了装饰器来帮助描述 API 的输入 / 输出和端点。 然后可以通过服务器上的一个端点访问该文档。

12.命令查询责任分离(CQRS)

命令查询责任分离(Command Query Responsibility Segregation,CQRS)的思想是,每个方法要么是执行操作(Command)的方法,要么是执行请求数据(Query)的方法。

在我们的示例应用程序的上下文中,不会在控制器中直接为一个端点创建数据库访问代码,而是创建一个组件(数据库服务) ,该组件具有getAllUsers()这样的方法,它将返回控制器服务可以调用的所有用户,从而将问题和答案分离到不同的组件中。

13.测试

测试你的 Nest 服务器将是必要的,确保部署时没有不可预见的安全性问题。

有两种不同类型的测试: 单元测试和 E2E 测试(端到端测试)。 单元测试是测试小代码片段或代码块,它可以像测试单个函数或 Controller、 Interceptor 或任何其他Injectable测试一样精细。

现在有很多流行的单元测试框架,Jasmine 和 Jest 是两个流行的框架。 Nest 提供了特殊的包,@nestjs/testing,用于在 *.spec.ts 及 *.test.ts 类中编写单元测试。

E2E 测试是另一种常用的测试形式,它不同于单元测试,它测试整个功能,而不是测试单个功能或组件,而这正是端到端测试这个名称的来源。

当应用程序变得非常庞大,以至于很难绝对地测试每一段代码和端点。 在这种情况下,您可以使用 E2E 测试从头到尾测试应用程序,以确保一切顺利进行。

对于 E2E 测试,Nest 应用程序可以再次使用 Jest 库来模拟组件。 除了 Jest 之外,还可以使用 supertest 库来模拟 HTTP 请求。