第3章第358回基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)

Posted 前端JavaScript

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第3章第358回基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)相关的知识,希望对你有一定的参考价值。

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


它不仅是又一个框架。你不必等待一个大型的社区,因为 Nest 建立在著名仓库 Express(https://github.com/expressjs/express)socket.io(https://github.com/socketio/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() 装饰器的类。


【第3章第358回】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)


在上一节中,我们为应用程序设置了入口点。现在,让我们来构建我们的第一个文件路径 /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 提供了一组自定义的装饰器,你可以使用它们来标记参数。

【第3章第358回】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)

你可以这样使用它们:


@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等等。并且他们可以通过构造函数注入控制器或另一组件。


【第3章第358回】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)


在上一节中, 我们构建了一个简单的 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(https://kamilmysliwiec.com/typescript-2-1-introduction-async-await)


模块(Modules)

模块是一个带有 @Module({}) 装饰器的类。该装饰器提供元数据,该框架用于组织应用程序结构。


【第3章第358回】基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)


现在,这是我们的 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) {}

}



>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


====================


以上是关于第3章第358回基于 TypeScript 的 Node.js 框架 Nest 正式版发布!(上)的主要内容,如果未能解决你的问题,请参考以下文章

第3章第314回TypeScript基础

[S3-E419]TypeScript 资源集

第3章第310回Webpack篇

[S3-E414]我们为什么选择TypeScript?

第2章第188回移动web动画设计的一点心得——css3实现跑步

第2章第300回原生JS与jQuery对AJAX的实现