本文为译文,原文地址:https://kamilmysliwiec.com/nest-final-release-is-here-node-js-framework-built-top-of-typescript,作者,@Kamil Myśliwiec
Nest 是一个强大的 Node.js Web 框架,可以帮助你轻松地构建高效,可扩展的应用程序。它采用现代 JavaScript,基于 TypeScript 构建,并结合了 OOP(面向对象编程)和 FP (函数式编程)的最佳概念。
它不仅是又一个框架。你不必等待一个大型的社区,因为 Nest 建立在著名仓库 Express 和 socket.io 之上。这意味着,你可以快速开始使用框架,而不必担心第三方插件的缺失。
核心概念
Nest 的核心概念是提供一种架构,帮助开发者实现层的最大分离,并且增加了应用程序的抽象。
安装
Git:
$ git clone https://github.com/kamilmysliwiec/nest-typescript-starter.git projectname
$ cd projectname
$ npm install
$ npm run start
NPM:
$ npm i --save @nestjs/core @nestjs/common @nestjs/microservices @nestjs/websockets @nestjs/testing reflect-metadata rxjs
设置应用程序
Nest 采用 ES6 和 ES7 (decorators
, async / await
)功能构建。这意味着,使用它的最简单的方法是 Babel
或 TypeScript
。
在本文中,我将使用 TypeScript
(它不是必须的!),我推荐大家选择这种方式。示例文件 tsconfig.json
如下:
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"noImplicitAny": false,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6"
},
"exclude": [
"node_modules"
]
}
记住 emitDecoratorMetadata
和 experimentalDecorators
必须设置为 true
。
让我们从头开始我们的应用程序。首先,我们必须被我们的应用程序创建入口模块(app.module.ts
):
import { Module } from '@nestjs/common';
@Module({})
export class ApplicationModule {}
此时,模块的元数据(metadata
)为空({}
),因为我们只想运行我们的应用程序,并没有加载任何控件或组件。
第二步,创建文件 index.ts
,并使用 NestFactory
基于我们的模块类来创建 Nest 应用程序实例。
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
const app = NestFactory.create(ApplicationModule);
app.listen(3000, () => console.log('Application is listening on port 3000'));
就这样。
Express 实例
如果要完全控制 express
实例的生命周期,你可以简单的传递已创建的对象作为 NestFactory.create()
方法的第二个参数,像这样:
import express from 'express';
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './modules/app.module';
const instance = express();
const app = NestFactory.create(ApplicationModule, instance);
app.listen(3000, () => console.log('Application is listening on port 3000'));
这意味着,你可以直接添加一些自定义配置(例如,设置一些插件,如 morgan
或 body-parser
)。
控制器(Controllers)
控制层(Controllers
)负责处理传入的 HTTP 请求。在 Nest 中,控制器是一个带有 @Controller()
装饰器的类。
在上一节中,我们为应用程序设置了入口点。现在,让我们来构建我们的第一个文件路径 /users
:
import { Controller, Get, Post } from '@nestjs/common';
@Controller()
export class UsersController {
@Get('users')
getAllUsers() {}
@Get('users/:id')
getUser() {}
@Post('users')
addUser() {}
}
正如你猜想的,我们刚刚创建了一个具有 3 种不同路径的路由:
GET: users
GET: users/:id
POST: users
没有必要重复 users
的每个路径了吧?
Nest 允许我们将额外的元数据传递给 @Controller()
装饰器 - 路径,这是每个路由的前缀。让我们重写我们的控制器:
@Controller('users')
export class UsersController {
@Get()
getAllUsers(req, res, next) {}
@Get('/:id')
getUser(req, res, next) {}
@Post()
addUser(req, res, next) {}
}
正如你看到的, Nest 控制器中的方法和 Express
中的简单路由具有相同的参数列表和行为。
如果你想了解更多关于 req
(请求),res
(响应)和 next
,你可以阅读简短的路由文档。在 Nest 中,它们是等价的。
但是有一个重要的区别。 Nest 提供了一组自定义的装饰器,你可以使用它们来标记参数。
Nest | Express |
---|---|
@Request() |
req |
@Response() |
res |
@Next() |
next |
@Session() |
req.session |
@Param(param?: string) |
req.params[param] |
@Body(param?: string) |
req.body[param] |
@Query(param?: string) |
req.query[param] |
@Headers(param?: string) |
req.headers[param] |
你可以这样使用它们:
@Get('/:id')
public async getUser(@Response() res, @Param('id') id) {
const user = await this.usersService.getUser(id);
res.status(HttpStatus.OK).json(user);
}
记住在文件的开头导入装饰器。
import { Response, Param } from '@nestjs/common';
UsersController
可以使用,但是我们的模块还不知道。让我们打开 ApplicationModule
并添加一些元数据。
import { Module } from '@nestjs/common';
import { UsersController } from "./users.controller";
@Module({
controllers: [ UsersController ]
})
export class ApplicationModule {}
你可以看到,我们只需要将 controller
插入 controllers
数组中,这就够了。
组件(Components)
几乎所有的东西都是组件,Service
, Repository
, Provider
等等。并且他们可以通过构造函数注入控制器或另一组件。
在上一节中, 我们构建了一个简单的 controller
,UsersController
。这个 controller
可以访问我们的数据(我知道这是一个假数据,但这并不重要)。这不是一个很好的解决方案。我们的控制器只能处理 HTTP 请求,并将更复杂的任务委托给服务(services
),这就是为什么我们要创建 UsersService
组件。
实际上,UsersService
应该从持久层调用适当的方法,例如, UsersRepository
组件。我们没有任何类型的数据库,所以我们再次使用假数据。
import { Component } from '@nestjs/common';
import { HttpException } from '@nestjs/core';
@Component()
export class UsersService {
private users = [
{ id: 1, name: "John Doe" },
{ id: 2, name: "Alice Caeiro" },
{ id: 3, name: "Who Knows" },
];
getAllUsers() {
return Promise.resolve(this.users);
}
getUser(id: number) {
const user = this.users.find((user) => user.id === id);
if (!user) {
throw new HttpException("User not found", 404);
}
return Promise.resolve(user);
}
addUser(user) {
this.users.push(user);
return Promise.resolve();
}
}
Nest 组件是一个简单的类,使用 @Component()
注释。
在 getUser()
方法中可以看到,我们使用了 HttpException
。它是 Nest 内置的异常,拥有两个参数,错误消息和状态代码。创建域异常是一个很好的做法,它应该扩展 HttpException
(更多见错误处理章节)。
我们的服务准备好了。让我们在 UsersController
中使用它。
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
getAllUsers(@Response req) {
this.usersService.getAllUsers()
.then((users) => res.status(HttpStatus.OK).json(users));
}
@Get('/:id')
getUser(@Response() res, @Param('id') id) {
this.usersService.getUser(+id)
.then((user) => res.status(HttpStatus.OK).json(user));
}
@Post()
addUser(@Response() res, @Body('user') user) {
this.usersService.addUser(req.body.user)
.then((msg) => res.status(HttpStatus.CREATED).json(msg));
}
}
如图所示,UsersService
将被注入到构造函数中。
使用 TypeScript
来管理依赖关系非常简单,因为 Nest 会根据类型识别你的依赖关系。像这样:
constructor(private usersService: UsersService)
这就是你要做的全部。还有一个重要的事是,你必须在 tsconfig.json
中将 emitDecoratorMetadata
选项设置为 true
。
如果你不是 TypeScript
爱好者,并且使用纯 JavaScript
,则必须按以下方式执行:
import { Dependencies, Controller, Get, Post, Response, Param, Body, HttpStatus } from '@nestjs/common';
@Controller('users')
@Dependencies(UsersService)
export class UsersController {
constructor(usersService) {
this.usersService = usersService;
}
@Get()
getAllUsers(@Response() res) {
this.usersService.getAllUsers()
.then((users) => res.status(HttpStatus.OK).json(users));
}
@Get('/:id')
getUser(@Response() res, @Param('id') id) {
this.usersService.getUser(+id)
.then((user) => res.status(HttpStatus.OK).json(user));
}
@Post()
addUser(@Response() res, @Body('user') user) {
this.usersService.addUser(user)
.then((msg) => res.status(HttpStatus.CREATED).json(msg));
}
}
这很简单,是么?
在这一刻,我们的应用程序甚至还未工作。
为何?因为 Nest 不知道有关 UsersService
的任何内容。此组件不是 ApplicationModule
的一部分,我们必须在那里添加:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ],
})
export class ApplicationModule {}
现在我们的应用程序将执行,但仍然有一个路由不能正常工作,就是 addUser
。为什么?因为我们正在尝试解析请求体(req.body.user
),而没有使用 express
的 body-parser
中间件。正如你知道的,可以通过 express
实例作为 NestFactory.create()
方法的第二个参数。
让我们安装插件:
$ npm install --save body-parser
然后在我们的 express
实例中设置它。
import express from 'express';
import * as bodyParser from 'body-parser';
import { NestFactory } from '@nestjs/common';
import { ApplicationModule } from './modules/app.module';
const instance = express();
instance.use(bodyParser.json());
const app = NestFactory.create(ApplicationModule, instance);
app.listen(3000, () => console.log('Application is listening on port 3000'));
Async / await
Nest 与 ES7 的 async / await
功能兼容。因此我们可以快速重写我们的 UsersController
:
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
async getAllUsers(@Response() res) {
const users = await this.usersService.getAllUsers();
res.status(HttpStatus.OK).json(users);
}
@Get('/:id')
async getUser(@Response() res, @Param('id') id) {
const user = await this.usersService.getUser(+id);
res.status(HttpStatus.OK).json(user);
}
@Post()
async addUser(@Response() res, @Body('user') user) {
const msg = await this.usersService.getUser(user);
res.status(HttpStatus.CREATED).json(msg);
}
}
看起来更好么?在这里你可以阅读更多关于 async / await。
模块(Modules)
模块是一个带有 @Module({})
装饰器的类。该装饰器提供元数据,该框架用于组织应用程序结构。
现在,这是我们的 ApplicationModule
:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ],
})
export class ApplicationModule {}
默认情况下,模块封装每个依赖关系。这意味着不可能在模块之外使用其组件或控制器。
每个模块也可以导入到另一个模块。实际上,你应该将 Nest 模块看做是 模块树。
我们将 UsersController
和 UsersService
移动到 UsersModule
。只需创建新文件,例如,users.module.ts
包含以下内容:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [ UsersController ],
components: [ UsersService ],
})
export class UsersModule {}
然后将 UsersModule
导入到 ApplicationModule
(我们的主应用程序):
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({
modules: [ UsersModule ]
})
export class ApplicationModule {}
这就是全部了。
可以看到出,使用 Nest 可以将代码自然地拆分成可分离和可重用的模块。
依赖注入
模块可以轻松地注入组件,看起来如下:
@Module({
controllers: [ UsersController ],
components: [ UsersService, ChatGateway ],
})
export class UsersModule implements NestModule {
constructor(private usersService: UsersService) {}
}
此外,组件还可以注入模块:
export class UsersController {
constructor(private module: UsersModule) {}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。