【译】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)

本文为译文,原文地址:https://kamilmysliwiec.com/nest-final-release-is-here-node-js-framework-built-top-of-typescript,做者,@Kamil Myśliwiechtml

Nest 是一个强大的 Node.js Web 框架,能够帮助你轻松地构建高效,可扩展的应用程序。它采用现代 JavaScript,基于 TypeScript 构建,并结合了 OOP(面向对象编程)和 FP (函数式编程)的最佳概念。node

它不只是又一个框架。你没必要等待一个大型的社区,由于 Nest 创建在著名仓库 Expresssocket.io 之上。这意味着,你能够快速开始使用框架,而没必要担忧第三方插件的缺失。git

核心概念

Nest 的核心概念是提供一种架构,帮助开发者实现层的最大分离,而且增长了应用程序的抽象。es6

安装

Git:github

$ git clone https://github.com/kamilmysliwiec/nest-typescript-starter.git projectname
$ cd projectname
$ npm install
$ npm run start

NPM:web

$ npm i --save @nestjs/core @nestjs/common @nestjs/microservices @nestjs/websockets @nestjs/testing reflect-metadata rxjs

设置应用程序

Nest 采用 ES6 和 ES7 (decoratorsasync / await)功能构建。这意味着,使用它的最简单的方法是 BabelTypeScripttypescript

在本文中,我将使用 TypeScript(它不是必须的!),我推荐你们选择这种方式。示例文件 tsconfig.json 以下:数据库

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": false,
    "noImplicitAny": false,
    "noLib": false,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es6"
  },
  "exclude": [
    "node_modules"
  ]
}

记住 emitDecoratorMetadataexperimentalDecorators 必须设置为 trueexpress

让咱们从头开始咱们的应用程序。首先,咱们必须被咱们的应用程序建立入口模块app.module.ts ):npm

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'));

这意味着,你能够直接添加一些自定义配置(例如,设置一些插件,如 morganbody-parser)。

控制器(Controllers)

控制层(Controllers)负责处理传入的 HTTP 请求。在 Nest 中,控制器是一个带有 @Controller() 装饰器的类。

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)

几乎全部的东西都是组件,ServiceRepositoryProvider等等。而且他们能够经过构造函数注入控制器或另外一组件

components

在上一节中, 咱们构建了一个简单的 controllerUsersController。这个 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),而没有使用 expressbody-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({}) 装饰器的类。该装饰器提供元数据,该框架用于组织应用程序结构。

modules

如今,这是咱们的 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 模块看作是 模块

咱们将 UsersControllerUsersService 移动到 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://www.javashuo.com/article/p-mqpqlfgd-md.html

相关文章
相关标签/搜索