一. 前言
在前五篇文章中我们已经完成了以下搭建起Nest服务器几个必要准备工作
- 一个极简的Nest服务器的搭建
- 封装Logger日志类,在控制台打印项目运行日志
- 自定义模块装饰器 / 控制器装饰器 / 参数装饰器
- 配置路径别名
- 实现项目热重载
二. 理解Nest中的路由系统
前期工作都准备完毕之后,这篇文章继续着手于下一个挑战,那就是
写接口
!挑战1:
app.controller.ts
中存在一个hello
接口已知接口路径是
/hello
,请求方式是GET
,返回值是hello nest
ts
// 导入Controller和Get装饰器
import { Get, Controller } from '@nestjs/common'
// 使用@Controller装饰器标记类为控制器
@Controller()
export class AppController {
// 使用@Get装饰器标记方法为处理GET请求的路由
@Get('hello')
hello(): string {
return 'hello'
}
@Get('info')
getInfo(): string {
return 'info'
}
}
如何在浏览器中输入http://localhost:4000/hello
时,正确的获取到hello
接口返回的hello nest
数据呢?
现在试一下,直接在浏览器中访问hello
接口会得到如下:
Cannot GET/hello ?
解释一下就是这通常意味着服务器无法找到请求的资源
拿前端页面来对比一下我觉得会更加的便于理解
拿Vue项目来说,当在浏览器中访问一个
router.ts/js
中不存在/或者说尚未的路由路径后, 通常会报404 notFound
这个异常,这代表着所访问的路由路径并没在路由系统中注册过,所以路由系统不认识这个路径,从而会抛出异常是不是和我们目前遇到的这个错误非常的相似? ~~ ~~ 因为我们无论是
原生node 还是基于原生node所创建的Express Koa 还是基于Express的Nest
所写的接口,也都是有路由系统的比如在
Nest
中@Controller装饰器就是设置统一的路由前缀
而在@GET/@Post等参数装饰器
中设置的是路由路径, 可以理解为vue-router
中的path
与childrenPath
的关系
三. 解决问题: 项目启动时,如何注册(映射)全部路由
nestApplication.ts
ts
// 导入元数据包
import 'reflect-metadata'
import express from 'express'
import type {
Express,
Request as ExpressRequest,
Response as ExpressResponse,
NextFunction as ExpressNextFunction
} from 'express'
import { Logger } from './logger'
import path from 'path'
export class NestApplication {
// 在内部私有化一个express实例
private readonly app: Express = express()
// protected readonly module等同于 this.module = module
constructor(protected readonly module) {}
// 初始化配置
async init() {
// 获取模块中所有的控制器类,准备做路由映射
const controllers = Reflect.getMetadata('controllers', this.module)
// 打印执行日志
Logger.log(`${this.module.name} dependencies initialized`, 'InstanceLoader')
// 路由映射的核心是要知道,什么样的请求方法,什么样的请求路径,请求是对应的那个处理函数
for (const Controller of controllers) {
// 创建每个控制器的实例
const controllerInstance = new Controller()
// 获取每个控制器的路径前缀
const prefix = Reflect.getMetadata('prefix', Controller) || '/'
// 打印执行日志: 提示开始路由解析
Logger.log(`${Controller.name} {${prefix}}`, 'RoutesResolver')
// 获取每个控制器类的原型
const controllerPrototype = Controller.prototype
// 遍历控制器类原型上的方法名
for (const methodName of Object.getOwnPropertyNames(controllerPrototype)) {
// 根据方法名获取该控制器类原型上的方法
const method = controllerPrototype[methodName]
// 获取该方法上的元数据
const httpMethod = Reflect.getMetadata('method', method)
const pathMetadata = Reflect.getMetadata('path', method)
// 如果方法名不存在(没有写GET/POST...请求装饰器的),则不处理
if (!httpMethod) continue
// 拼出完整的路由路径
const routePath = path.posix.join('/', prefix, pathMetadata)
// 配置路由,当客户端以httpMethod方法请求routePath路径的时候,会由对应的函数进行处理
this.app[httpMethod.toLowerCase()](
routePath,
(req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => {
const result = method.call(controllerInstance)
res.send(result)
}
)
Logger.log(`Mapped {${routePath}, ${httpMethod}} route`, 'RoutesResolver')
}
}
}
// 启动HTTP服务器
async listen(port: number) {
this.init()
// 调用express实例的listen方法,启动一个HTTP服务器,监听port端口
this.app.listen(port, () => {
Logger.log(`Application is running on http://localhost:${port}`, 'NestApplication')
})
}
}
总结
init()
方法扫描模块中的所有控制器- 遍历控制器中的方法,检查每个方法的 HTTP 请求装饰器(如
@Get
、@Post
) - 基于装饰器信息注册路由,建立 HTTP 路由与控制器方法的映射