本节我们实现一个简化版的启动nestjs服务的功能
官方启动服务代码
我们先参照官方文档构建一个最简单的Nest服务,代码如下:
1 2 3 4 5 6 7 8 9 10
| import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
|
1 2 3 4 5 6 7 8 9
| import { Module } from '@nestjs/common'; import { AppController } from './app.controller';
@Module({ controllers: [AppController], }) export class AppModule {}
|
1 2 3 4 5 6 7 8 9 10
| import { Controller, Get } from "@nestjs/common";
@Controller() export class AppController { @Get() getHello(): string { return 'Hello' } }
|
我们主要用到了两个包@nestjs/core和@nestjs/common,其中用到了core包中的NestFactory类,以及common包中的Module, Controller, Get这几个装饰器。
接下来我们自己手动来实现这部分功能
目录结构
1 2 3 4 5 6 7 8 9 10 11 12
| # 目录结构如下 - @nestjs - common - index.ts - module.decorator.ts - controller.decorator.ts - http-methods.decorator.ts - core - index.ts - nest-factory.ts - nest-application.ts - logger.ts
|
手写实现启动服务所需的基本功能,不按照源码实现,只是一个思路
启动服务所需的代码与前边是一致的,只是不再引入官方包,而是使用我们自己封装的
common
common包目前定义了一些装饰器,这些装饰器目前只用来定义一些元数据,由于服务启动时,需要读取元数据进行使用,所以我们先展示一下common包中我们现在实现了哪些内容。
index做统一导出:
1 2 3 4
| export * from './module.decorator' export * from './controller.decorator' export * from './http-methods.decorator'
|
@Module目前只做了一件事,就是把传递的controllers保存到元数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import 'reflect-metadata'
interface ModuleMetadata { controllers: Function[] }
export function Module(metadata: ModuleMetadata): ClassDecorator { return (target: Function) => { Reflect.defineMetadata('controllers', metadata.controllers, target) } }
|
@Controller用来把路径前缀保存到元数据,其定义方式较多,所以做了一些判断,注意一下函数重载,这是TS支持的功能,可以定义各种重载的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import 'reflect-metadata'
interface ControllerOptions { prefix: string }
export function Controller(): ClassDecorator export function Controller(prefix: string): ClassDecorator export function Controller(options: ControllerOptions): ClassDecorator
export function Controller(prefixOrOptions?: string | ControllerOptions): ClassDecorator { // 根据不同的类型赋值 let options:ControllerOptions = { prefix: '' } if (typeof prefixOrOptions === 'string') { options.prefix = prefixOrOptions } else if (typeof prefixOrOptions === 'object') { options = prefixOrOptions } return (target: Function) => { Reflect.defineMetadata('prefix', options.prefix || '', target) } }
|
@Get用来将请求方法和路径保存到方法的元数据,我们目前只实现了Get
1 2 3 4 5 6 7 8 9 10 11
| import 'reflect-metadata'
export function Get(path:string = ''): MethodDecorator { return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { Reflect.defineMetadata('path', path, descriptor.value) Reflect.defineMetadata('method', 'GET', descriptor.value) } }
|
core
core包是启动服务所需的内容,在index.ts中主要是统一导出了其他文件中的内容
1 2 3 4
| export * from './nest-factory' export * from './logger' export * from './nest-application'
|
在main.ts中我们通过new NestFactory(AppModule)来生成实例,这是NestFactory的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Logger } from "./logger"; import { NestApplication } from "./nest-application";
export class NestFactory { static async create(module: any) { Logger.log('Starting Nest application...', 'NestFactory') const app = new NestApplication(module) return app } }
|
实际的创建实例操作是在NestApplication中实现的,其内部完成了express应用的创建,路由的解析和路由注册
这些功能的实现,得益于我们的装饰器定义了相关的元数据,在这里取出来进行解析,装饰器的内容在common包中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
import * as express from 'express' import type { Express, Request, Response, NextFunction } from 'express' import 'reflect-metadata' import { Logger } from './logger' import * as path from 'path' export class NestApplication { private readonly app = express()
constructor(protected readonly module: any) {} async init() { const controllers = Reflect.getMetadata('controllers', this.module) || [] Logger.log(`AppModule dependencies initialized`, 'InstanceLoader') for (const Controller of controllers) { const controller = new Controller() const prefix = Reflect.getMetadata('prefix', Controller) || '/' Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver') const controllerPrototype = Reflect.getPrototypeOf(controller) for (const methodName of Object.getOwnPropertyNames(controllerPrototype)){ const method = controllerPrototype[methodName] const httpMethod = Reflect.getMetadata('method', method) const httpPath = Reflect.getMetadata('path', method) if (!httpMethod) continue; const routePath = path.posix.join('/', prefix, httpPath) this.app[httpMethod.toLowerCase()](routePath, (req: Request, res: Response, next: NextFunction) => { const result = method.call(controller) res.send(result) }) Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
} } Logger.log('Nest application successfully started', 'NestApplication') } async listen(port: number) { await this.init() this.app.listen(port, () => { Logger.log(`Application is running on http://localhost:${port}`, 'NestApplication') }) } }
|
Logger用来打印日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { green, yellow } from 'cli-color'
export class Logger { private static lastLogTime = Date.now() static log(message: string, context: string = '') { const timestamp = new Date().toLocaleString() const pid = process.pid const currentTime = Date.now() const timeDiff = currentTime - this.lastLogTime console.log(`${green('[Nest]')} ${green(pid.toString())} - ${timestamp} ${green('LOG')} [${yellow(context)}] ${green(message)} +${yellow(timeDiff)}ms`) this.lastLogTime = currentTime } }
|
至此,我们实现了简单的路由解析和处理程序,后续还有更多内容要处理,我们在后边在讲