MVC框架之Nestjs

最近看到了一篇介绍node后端的文章,偶然听说了nestjs这个框架。看了一下觉得挺有意思的,就在这里分享一下。

综述

Nestjs是一个基于Expressjs封装的node后端框架,天然为typescript量身定制了语法支持,github上有近15K的star。得益于es6的Reflect,nest拥有了@annotation注入依赖,在中量级node应用中有较高的知名度。

安装

文章开始前,我们先构建一个nest项目,再进一步谈论里面的模块。Nestjs提供了自己的cli脚手架,两行命令就可以创建一个新的项目。

npm i -g @nestjs/cli
nest new project-name

然后yarn start,在localhost:3000可以看到Hello World了。嗯,这就是我喜欢node的原因——简单快速。

OK,看一下模版代码,src里的结构如下:

src
|--app.controller.ts
|--app.service.ts
|--app.module.ts
|--main.ts

可以很直观的联系到传统MVC的分层结构:

  • controller就是传统意义上的控制器,提供api接口

  • service又称为Provider,是一系列服务、repo、工厂方法、helper的总称

  • module是controller和provider的集合,类似于namespace的概念,支持module内部controller和provider的注入互相关联

上面这些概念我会逐步在后文介绍使用。先看一下main.ts,这个是入口文件。这里利用了一个叫NestFactory方法创建了这个nest应用。有Spring开发经验的后端对此一定不会太过陌生。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

Controller

Controller在上面也提了,就是传统意义上的控制器,用户接受request再返回response。

Routing

装饰器@Controller很好用,可以将路由直接标志在类和函数头上,很简洁地实现了路由去中心化。

// nest
@Controller('hello')
export class AppController {

  @Get('world')
  getHello(): string {
    return 'Hello World';
  }
}

相比于express的原始路由,nest装饰器显然更具工程化。

// express
AppController.get('/world', function getHello() {
    return 'Hello World'
});

router.use('/hello', AppController);

请求

请求类的装饰器封装了express handler的参数和一些资源,使用方法如下例所示:@Request()映射的就是express handler(req, res, nest)第一个参数req。

@Controller('hello')
export class AppController {

  @Get('world/:id')
  getHello(@Response() res, @Param('id') id): string {
    res.status(HttpStatus.OK).json('hello world')
  }
}

我这里列了几个nest装饰器和handler资源的映射关系。开发中常用的req.paramsreq.headers也做了封装,coding时可以免去一些繁琐的判断语句。想看完整版可以点这里

Decorator Handler
@Request() req
@Response() res
@Next() nest
@Param(key?: string) req.params / req.params[key]
@Headers(name?: string) req.headers / req.headers[name]

资源

Nest对http的资源请求也做了配置,一系列常用的装饰器如@Get用来指明request方法(支持通配),@Header@HttpCode用于指定respose的返回头和状态码等等。有兴趣的朋友可以去开发者文档具体阅读细节,这里不再深入研究。

@Get('ab*cd')
@Header('Cache-Control', 'none')
@HttpCode(204)
findAll() {
  return 'This route uses a wildcard';
}

Provider

Provider就是MVC开发中的服务,是一系列services, repositories, factories, helpers等等的总称。我们定义一个provider如下,Nest就是利用一个叫@Injectable()的装饰器实现注入依赖。

// app.service.ts

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

如下,Controller中使用的是构造器注入的方式。注意下面加了private readonly,这是ts快速初始化私有域的语法糖。

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

Module

OK,上面提到了Provider注入,那怎么将Controller和Provider相关联呢?Module出场了。先看代码:

// app.module.ts

@Module({
  imports: [otherModule],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}

Module还是挺直观的:provider和controller就是上文提到的服务和控制器;imports里引入的是其他module的实例。

module structure

OK,来总结一下什么是module

A module is a class annotated with a @Module() decorator. The @Module() decorator provides metadata that Nest makes use of to organize the application structure.

简单来说,Module是个namespace或是scope的概念,是一系列Controller和Provider的方法集,也可以看成一种应用的组织形式。在Module内部,nest实现了注入关联。

Middleware

Express还有个概念middleware,感兴趣的朋友可以看我的博客《Express Middleware》。Next封装了express,自然也少不了middleware。Nest middleware是依靠继承一个叫NestMiddleware类实现的,它也可以通过注入实现。

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req, res, next) {
    console.log('Request...');
    next();
  }
}

我们在Module添加Middleware配置。如下所示,http请求会在到达AppController前执行middleware的use——打印Request...

// app.module.ts
export class ApplicationModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(AppController);
  }
}

Nest还提供了函数中间件、链式中间件、全局中间件等等配置方式,感兴趣的朋友可以参阅开发者文档。

小结

今天科普了一款typescript的后端框架——nestjs,它是我见过的设计最优雅的node MVP框架。相比于轻量级框架express,nest更具工程化、规范化;注入依赖,也让人有一种在node里写Spring的感觉。不过缺点也很直观,相比于express,启动速度慢了三五倍;开发中,热加载体验也远不如express和koa。选择框架时,还是需要tradeoff的。

突然想起《神雕侠侣》中的一句话,“重剑无锋,大巧不工”——剑技不在剑锋而在个人修行。现实开发中,我们会碰到各式各样的应用场景,并不存在放之四海而皆准的框架;在平日“修行”中,应尽量扩充知识库,若脑海中只有一个选项,“歧路亡羊”是迟早的事。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容