欢迎订阅Nest.js口袋书专栏
如果您觉得这篇文章有帮助的话!给个点赞和评论支持下吧,感谢~
作者:前端小王hs
阿里云社区博客专家/清华大学出版社签约作者✍/者作问访万百NDSC/B站千粉前端up主
核心文件
在第三节我们提到了src是项目的核心目录,其包含的文件如下所示:
那么现在,我们逐一来分析里面的内容,并最后总结整个过程
当然,这里需要提一嘴的是,在官方的中文文档 中,也有对src目录的文件介绍,所以如果读者您 对控制器 、根模块 、服务等概念熟悉的话,可以跳过这一节内容。介绍如下图所示:
app.controller.spec.ts
这是一个基于Jest
的测试文件 ,其内容与在根目录下test
目录中的app.e2e-spec.ts
逻辑相同,主要用于测试app.controller.ts
控制器中定义的路由处理逻辑的单元测试文件
这里需要注意的是,带有.spec.ts
后缀的文件,在nest
中就是用于测试的文件,这是一种约定俗成的命名方式
同时,在本节中笔者将不会对该文件进行分析,但等我们熟悉了请求的逻辑是如何被响应及处理时,笔者将会重新讲解如何使用Jest
进行测试
app.controller.ts
这是一个简单的控制器 示例,controller
的翻译即为控制器
那么我们该如何去理解控制器 的作用?如果您了解过express
框架,并对express
框架有一定的代码实践,那么应该对路由 和路由处理程序 熟悉,而控制器 即对应路由
如果您没了解过express
,可查看笔者在去年发布的vue3+ts+mysql+express+源码+实战+全栈|后台管理系统项目项目
我们知道,在express
中的路由 的作用是负责处理传入的HTTP
请求并返回响应,而控制器也是起到同样的作用
我们来看一下控制器中的代码
ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
第一行代码从@nestjs/common
模块中导入了两个装饰器
,分别是Controller
和Get
@nestjs/common
模块是nest
框架的核心模块,提供了大量的用于构建nest
应用程序的基础装饰器、类、实用程序、异常和错误 等,可以说在后面的代码实践中,我们也会多次的使用该模块去导入基础装饰器、类、实用程序、异常和错误等
由于我们的口袋书内容是循序渐进,由浅至深 的,所以笔者会在使用到某个装饰器或其他导入内容时再对其进行介绍
这里我们需要知道的是,nest
中使用@Controller
标识这是一个控制器
假设我们要定义一个包含多个路由 的路由模块 ,在express
中的代码如下所示:
ts
// 登录注册模块路由
// 导入express框架
const express = require('express')
// 使用express框架的路由
const router = express.Router()
// 导入login的路由处理程序模块
const loginHandler = require('../router_handle/login')
// 注册
router.post('/register',loginHandler.register)
// 登录
router.post('/login',loginHandler.login)
// 向外暴露路由
module.exports = router
而在nest
中,就需要导入Controller
并进行标识
上述express
的代码示例中,我们可以看到每个路由 的都是使用了router.post()
方法,这是规定了该路由的HTTP
请求方式为POST
。而在我们当前的控制器 文件中,是通过装饰器 标识该路由的请求方式,如代码中的@Get
,即标识该路由的请求方式为GET
此外,我们在express
的代码示例中,可看到router.post()
方法的参数除了请求路径 外,还有loginHandler.register
(以注册为例),这是路由的处理程序 (执行逻辑区域),是从../router_handle/login
中导入的
我们可以想象,在../router_handle/login
中的代码如下所示:
ts
exports.register = (req, res) => {
// 注册逻辑,如判断当前账号是否已存在、对密码加密等
}
"王哥,现在我知道了这个控制器 就是express
的路由,但是在B站的项目里,注册接口是有前缀的,如/api/register
,这些路径写在哪呢?"
"真是个好问题,如果我们想要在nest
中给接口添加该模块 的前缀(假设登录模块的前缀为/api
),并且给指定的路由添加前缀(假设注册功能的前缀为/register
),可以这样写:
ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {}
@Post('register')
register(): string {
return this.appService.register();
}
}
那么这样,只有当请求方式为post
且路径为/api/register
时,才会执行注册逻辑 "
而在nest
中,路由的处理程序位于service
中,可以看到在第二行代码中导入了该控制器 对应的路由处理程序AppService
,并在@Get
请求中调用了位于./app.service
的getHello()
方法,由于该方法返回的是一段字符串 ,所以添加了返回类型string
现在,我们明白了一个HTTP
请求会途径控制器 ,并在服务中找到对应的处理程序
但我们还有一段代码没有分析,就是
ts
constructor(private readonly appService: AppService) {}
这一段代码使用了TypeScript
的类构造函数、private
和readonly
两个修饰符,以及DI (依赖注入)概念,关于这一点,我们会在讲解service
时进行解释
码字不易,如果觉得本文章不错,还请添加订阅并点赞喔~
app.service.ts
我们还是直接先来分析这段代码,代码如下:
ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
第一行代码中,从nest
的核心模块@nestjs/common
中导入了Injectable
,该单词意为可注射/注入的
,意味这这个向外导出的类 是一个可注入的 ,那么问题来了,注入到哪?
非常简单,我们回想在哪里导入了该AppService
?没错,在控制器 中,并在constructor
这个类构造函数中进行了注入 操作,那么问题又来了,注入的作用是什么?
我们知道面向对象开发的类与对象 概念,类就是一个模板 ,而对象 是模板的一个具体实现,就好比做月饼需要有个模具,不同的模具能够做出不同样式的月饼
在TavaScript
中,我们通常会使用如下代码去实现一个实例
ts
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet() {
console.log(`我的名字是 ${this.name},我今年 ${this.age} 岁.`);
}
}
// 使用new关键字和构造函数"手动"创建 Person 类的一个实例
const person = new Person("前端小王hs", 24);
// 调用实例的方法
person.greet(); // 输出: 我的名字是前端小王hs,今年24岁.
而在nest
中,我们无需手动的创建一个实例,这是由依赖注入容器来完成的
回看源码的控制器 ,是通过参数的形式将这个类 AppService
作为注入的依赖的类型(模板),创建了一个名为appService
的实例,那么理所当然的是该实例具有了getHello()
方法,进而可以直接在控制器中进行调用类中定义的方法
而对于控制器 中的private
,则表示这个实例appService
,只能在当前类(AppController
)中访问;readonly
则表示实例appService
如果在类AppService
中已经被赋值,就不能重新赋值了。这样做的好处是保证service
的不可变性,增加代码的稳定性
对于AppService
中的逻辑,就非常简单了,就是定义了一个方法getHello()
app.module.ts
从名字带有module
可知,这是本程序的根模块 。在nest
中的开发是使用模块化的方式组织代码,每个模块都包含一组(数组形式 )控制器(controllers) 、提供者(providers) 、以及其他模块,也就是下列代码@Module
中的三个参数
ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
"怎么哪都有你------nestjs/common
"
"因为我是核心模块,嘿嘿"
在AppModule
中需要使用Module
装饰器来定义模块的结构,Module
即意为"模块"
在这段代码中,清晰地告诉了nest
谁是控制器 和谁是提供者 ,当nest
启动时,就会根据当前@Module
中的结构去进行初始化。在复杂的大型项目中,几乎都会在imports
中导入许多的模块,而通过nest
模块化的特性,使得nest
相比于其他的框架更支持大型项目的扩展
回想一下,如果是express
,可没有这样的模块化管理方式,不是吗?
main.ts
在任何程序中,mian.ts
都是作为程序的入口文件,nest
也不例外
ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
在这段代码中,首先定义了一个异步的bootstrap()
函数,其是用来启动应用程序的
在函数内部,使用了NestFactory.create()
方法创建了一个nest
实例,并传入了AppModule
,告诉nest
如何去使用AppModule
中定义的结构去构建应用程序
最后,监听了3000
端口,所以可以在本地的3000
端口访问nest
逻辑
nest
中采取模块化的开发方式,在Module
中定义了谁是控制器(路由) ,谁是提供者(路由处理程序) ,最后将Module
作为参数传入入口文件 ,告诉nest
依据这个模块的结构去生成对应的应用程序
本篇最后
码字不易,这一过程涉及到如何将晦涩的概念以通俗的言语表达出来,如果感觉这篇文章对您有帮助,笔者希望能得到您的评论+关注 !您的评论+关注是我更文的最大动力!
如果您发现有错字,还请见谅并给予指正建议,笔者会在最短时间内修改并私聊感谢!
如果由于不可抗拒因素导致拖更,还请您见谅!
如果需进一步技术交流,请您在首页联系方式内联系我!