Nest.js口袋书(4)src目录

欢迎订阅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模块中导入了两个装饰器,分别是ControllerGet

@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.servicegetHello()方法,由于该方法返回的是一段字符串 ,所以添加了返回类型string

现在,我们明白了一个HTTP请求会途径控制器 ,并在服务中找到对应的处理程序

但我们还有一段代码没有分析,就是

ts 复制代码
constructor(private readonly appService: AppService) {}

这一段代码使用了TypeScript的类构造函数、privatereadonly两个修饰符,以及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依据这个模块的结构去生成对应的应用程序

本篇最后

码字不易,这一过程涉及到如何将晦涩的概念以通俗的言语表达出来,如果感觉这篇文章对您有帮助,笔者希望能得到您的评论+关注 !您的评论+关注是我更文的最大动力!

如果您发现有错字,还请见谅并给予指正建议,笔者会在最短时间内修改并私聊感谢

如果由于不可抗拒因素导致拖更,还请您见谅!

如果需进一步技术交流,请您在首页联系方式内联系我!

相关推荐
小突突突1 小时前
Spring框架中的单例bean是线程安全的吗?
java·后端·spring
iso少年1 小时前
Go 语言并发编程核心与用法
开发语言·后端·golang
掘金码甲哥1 小时前
云原生算力平台的架构解读
后端
码事漫谈2 小时前
智谱AI从清华实验室到“全球大模型第一股”的六年征程
后端
码事漫谈2 小时前
现代软件开发中常用架构的系统梳理与实践指南
后端
Mr.Entropy2 小时前
JdbcTemplate 性能好,但 Hibernate 生产力高。 如何选择?
java·后端·hibernate
YDS8292 小时前
SpringCloud —— MQ的可靠性保障和延迟消息
后端·spring·spring cloud·rabbitmq
无限大63 小时前
为什么"区块链"不只是比特币?——从加密货币到分布式应用
后端
洛神么么哒3 小时前
freeswitch-初级-01-日志分割
后端
蝎子莱莱爱打怪3 小时前
我的2025年年终总结
java·后端·面试