文: 達孚(滬江Web前端架構師)前端
本文原創,轉至滬江技術node
首先上一下項目地址(:>):git
Nest:github.com/nestjs/nestgithub
Nesk:github.com/kyoko-df/ne…express
Nest是一個深受angular激發的基於express的node框架,按照官網說明是一個旨在提供一個開箱即用的應用程序體系結構,容許輕鬆建立高度可測試,可擴展,鬆散耦合且易於維護的應用程序。編程
在設計層面雖說是深受angular激發,但其實從後端開發角度來講相似於你們熟悉的Java Spring架構,使用了大量切面編程技巧,再經過裝飾器的結合徹底了關注上的分離。同時使用了Typescript(也支持Javascript)爲主要開發語言,更保證了整個後端系統的健壯性。bootstrap
那首先爲何須要Nest框架,咱們從去年開始大規模使用Node來替代原有的後端View層開發,給予了前端開發除了SPA之外的先後端分離方式。早期Node層的工做很簡單-渲染頁面代理接口,但在漸漸使用中你們會給Node層更多的寄託,尤爲是一些內部項目中,你讓後端還要將一些現有的SOA接口進行包裝,對方每每是不肯意的。那麼咱們勢必要在Node層承接更多的業務,包括不限於對數據的組合包裝,對請求的權限校驗,對請求數據的validate等等,早期咱們的框架是最傳統的MVC架構,可是咱們翻閱業務代碼,每每最後變成複雜且很難維護的Controller層代碼(從權限校驗到頁面渲染一把擼到底:))。後端
那麼咱們如今看看Nest能夠作什麼?從一個最簡單的官方例子開始看:緩存
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
複製代碼
這裏就啓動了一個nest實例,先不看這個ValidationPipe,看ApplicationModule的內容:bash
@Module({
imports: [CatsModule],
})
export class ApplicationModule implements NestModule {
configure(consumer: MiddlewaresConsumer): void {
consumer
.apply(LoggerMiddleware)
.with('ApplicationModule')
.forRoutes(CatsController);
}
}
複製代碼
@Module({
controllers: [CatsController],
components: [CatsService],
})
export class CatsModule {}
複製代碼
這裏看到nest的第一層入口module,也就是模塊化開發的根本,全部的controller,component等等均可以根據業務切分到某個模塊,而後模塊之間還能夠嵌套,成爲一個完整的體系,借用張nest官方的圖:
在nest中的component概念其實一切能夠注入的對象,對於依賴注入這個概念在此不作深刻解釋,能夠理解爲開發者不須要實例化類,框架會進行實例化且保存爲單例供使用。
@Controller('cats')
@UseGuards(RolesGuard)
@UseInterceptors(LoggingInterceptor, TransformInterceptor)
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get(':id')
findOne(
@Param('id', new ParseIntPipe())
id,
): Promise<Cat> {
return this.catsService.findOne(id);
}
}
複製代碼
Controller的代碼很是精簡,不少重複的工做都經過guards和interceptors解決,第一個裝飾器Controller能夠接受一個字符串參數,即爲路由參數,也就是這個Controller會負責/cats路由下的全部處理。首先RolesGuard會進行權限校驗,這個校驗是本身實現的,大體結構以下:
@Guard()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(request, context: ExecutionContext): boolean {
const { parent, handler } = context;
const roles = this.reflector.get<string[]>('roles', handler);
if (!roles) {
return true;
}
// 自行實現
}
}
複製代碼
context能夠獲取controller的相關信息,再經過反射拿到handler上是否有定義roles的元信息,若是有就能夠在邏輯里根據本身實現的auth方法或者用戶類型來決定是否讓用戶訪問相關handler。
interceptors即攔截器,它能夠:
本示例有兩個攔截器一個用來記錄函數執行的時間,另外一個對結果進行一層包裝,這兩個需求都是開發中很常見的需求,並且攔截器會提供一個rxjs的觀察者流來處理函數返回,支持異步函數,咱們能夠經過map()來mutate這個流的結果,能夠經過do運算符來觀察函數觀察序列的執行情況,另外能夠經過不返回流的方式,從而阻止函數的執行,LoggingInterceptor例子以下:
@Interceptor()
export class LoggingInterceptor implements NestInterceptor {
intercept(dataOrRequest, context: ExecutionContext, stream$: Observable<any>): Observable<any> {
console.log('Before...');
const now = Date.now();
return stream$.do(
() => console.log(`After... ${Date.now() - now}ms`),
);
}
}
複製代碼
回到最初的ValidationPipe,它是一個強大的校驗工具,咱們看到前面的controller代碼中插入操做中有一個CreateCatDto,dto是一種數據傳輸對象,一個dto能夠這樣定義:
export class CreateCatDto {
@IsString() readonly name: string;
@IsInt() readonly age: number;
@IsString() readonly breed: string;
}
複製代碼
而後ValidationPipe會檢查body是否符合這個dto,若是不符合就會就會執行你在pipe中設置的處理方案。具體是如何實現的能夠再寫一篇文章了,因此我推薦你看nest中文指南(順便感謝翻譯的同窗們)
示例的完整代碼能夠看01-cats-app
也就是說業務團隊中的熟練工或者架構師能夠開發大量的模塊,中間件,異常過濾器,管道,看守器,攔截器等等,而不太熟練的開發者只須要完成controller的開發,在controller上像搭積木般使用這些設施,即完成了對業務的完整搭建。
雖然我我的很喜歡Nest,可是咱們公司已經有一套基於koa2的成熟框架Aconite,而Nest是基於express的,查看了下Nest的源碼,對express有必定的依賴,可是koa2和express在都支持async語法後,差別屬於可控範圍下。另外nest接受一個express的實例,在nesk中咱們只須要調整爲koa實例,那麼也能夠是繼承於koa的任何項目實例,咱們的框架在2.0版本也是一個在koa上繼承下來的node框架,基於此,咱們只須要一個簡單的adapter層就能夠無縫接入Aconite到nesk中,這樣減小了nesk和內部服務的捆綁,而將全部的公共內部服務整合保留在Aconite中。Nest對於咱們來講只是一個更完美的開發範式,不承接任何公共模塊。
因此咱們須要的工做能夠簡單總結爲:
支持Koa咱們在Nest的基礎上作了一些小改動完成了Nesk來兼容Koa體系。咱們只須要完成Nesk和Aconite中間的Adapter層,就能夠完成Nesk的落地,最後啓動處的代碼變成:
import { NeskFactory } from '@neskjs/core';
import { NeskAconite } from '@hujiang/nesk-aconite';
import { ApplicationModule } from './app.module';
import { config } from './common/config';
import { middwares } from './common/middlware';
async function bootstrap() {
const server = new NeskAconite({
projectRoot: __dirname,
middlewares,
config
});
const app = await NeskFactory.create(ApplicationModule, server);
await app.listen(config.port);
}
複製代碼
最後Nest有不少@nest scope下的包,方便一些工具接入nest,若是他們與express沒有關係,咱們實際上是能夠直接使用的。可是包內部每每依賴@nest/common或者@nesk/core,這裏可使用module-alias,進行一個重指向(你能夠嘗試下graphql的例子):
"_moduleAliases": {
"@nestjs/common": "node_modules/@neskjs/common",
"@nestjs/core": "node_modules/@neskjs/core"
}
複製代碼
Nesk的地址Nesk,咱們對Nesk作了基本流程測試目前覆蓋了common和core,其它的在等待改進,歡迎一切願意一塊兒改動的開發者。
其實從一個更好的方面來講,咱們應當容許nest接受不一樣的底層框架,即既可使用express,也可使用koa,經過一個adapter層抹平差別。不過這一塊的改形成本會大一些。
另外一方面nest有一些自己的不足,在依賴注入上,仍是選擇了ReflectiveInjector,而Angular已經開始使用了StaticInjector,理論上StaticInjector減小了對Map層級的查找,有更好的性能,這也是咱們決定分叉出一個nesk的緣由,能夠作一些更大膽的內部代碼修改。另外angular的依賴注入更強大,有例如useFactory和deps等方便測試替換的功能,是須要nest補充的.
最後全部的基於Koa的框架都會問到一個問題,能不能兼容eggjs(:)),其實不管是Nest仍是Nesk都是一個強制開發規範的框架,只要eggjs還創建在koa的基礎上,就能夠完成集成,只是eggjs在啓動層面的改動較大,並且開發範式和nest差別比較多,二者的融合並無顯著的優點。
總之Node做爲一個比較靈活的後端開發方式,每一個人心中都有本身以爲合適的開發範式,若是你喜歡這種方式,不妨嘗試下Nest或者Nesk。
2019年,iKcamp原創新書《Koa與Node.js開發實戰》已在京東、天貓、亞馬遜、噹噹開售啦!