NestJS 快速入门教程
NestJS视频列表
01.NestJS简介和优势介绍
为什么要学习NestJS
NestJS前端程序员走向全栈必学的Node.js框架。我这里罗列了三个基本理由。
1.走向高级前端的必经之路。 个人认为作为前端程序员有四个阶段:
-
第一阶段:需要会HTML+CSS+JavaScript,这个阶段我们成为切图仔,作的就是把设计图编写成应用界面的工作。
-
第二阶段:中级前端开发者,这个阶段你需要熟练使用Vue、或者React、或者Angular来开发公司项目。
-
第三阶段:高级前端开发者,制作复杂的SPA应用,并能进行全栈开发,参与一定的架构设计和讨论。
-
第四阶段:系统架构师,负责公司项目的总体架构,至少负责前端架构工作,需要多年开发经验积累。
所以说如果你想进阶高级前端工程师,就必须从单一的前端开发走向全栈,而我最推荐的框架就是NestJS了。
2.NestJS是成熟的Node.js服务端开发架构框架 有经验的小伙伴都知道Node.js是可以开发后端,但是它就像一堆零散的乐高积木,要想做出东西,还需要我们花大量心思去封装很多的模块性功能。NestJS是基于Node.js的后端开发框架,它里边封装了大量常用的后端开发模块,大大减轻了我们的开发复杂度。NestJS也是目前Node.js后端开发中最流行的开发框架。 如果你非要拿NestJS和Node.js比较,这里有个不太恰当的比喻,Node.js相当于一个漂亮的姑娘,姑娘对你好不好,愿意为你作什么,需要你慢慢的培养感情。而NestJS就是大型娱乐中心,里边你想要的项目全部为你准备好了,可以喝花酒、可以唱商K、可以SPA +柔式按摩、还可以休息住宿,也就是我们常说的一条龙服务,绝对可以让你爽到起飞。 3.学会NestJS能获得更高的工资 对于个人来说更重要的是在国内开发环境中可以看到会NestJS框架的前端开发人员,会比不会的人员薪资高一个级别,所以你本着一切向前看的初衷,也应该学习NestJS。
什么是NestJS
那到底什么是NestJS那?官方是这样介绍如下。
NestJS提供了一个开箱即用的应用程序架构,允许开发人员和团队创建高度可测试,可扩展,松散耦合且易于维护的应用程序。
这段话虽然简单,但也阐述了NestJS的主要特点,它解决的是Node.js生态开发中的架构问题,也就是说使用它,你就就会获得不错的后端架构。而且它还表明了这个架构的特点,高度可测试、可扩展、松散耦合易于维护。 NestJS还有一些特点,它使用渐进式JavaScript,内置并完全支持TypeScript(但你也可以使用纯JS来进行开发)并结合了OOP(面向对象编程),FP(函数式编程)和FRP(函数式响应编程)的元素。这些特点保证了NestJS是一个高度可扩展和松散耦合度的框架。也就我们开发时常说的高内聚低耦合。
NestJS的网址
学开源的框架系统,最好的学习手段就是看官方文档。所以这里给出它的两个官方网址,本视频也是参考这两个网站进行学习编写录制的。
官方网址:https://nestjs.com 中文文档:https://www.nestjs.com.cn/
本教程的产出的知识和观点也主要依据了这两个网站,但因本人不是专职老师,是一个常年编写代码的码农,讲课水平有限,一定会有所疏漏,所以还请小伙伴们多多指教。 我们打开官方网址,可以看到其实有很多著名的应用都在使用NestJS框架。值得一说的是:
-
阿里的双十一前端解决方案中就大量的使用了NestJS框架。
-
腾讯视频在支持国庆阅兵直播的高并发中也使用了NestJS框架。
在国内要求性能和高并发的应用中NestJS技术出现率还是很高的,而且最近两年越来越多的服务端技术使用了它。
讨论、参与和加群方式
为了更好的学习,我建立了一个微信群,我会在群里随时分享我的学习感想。当然你也可以加我微信好友,方便指出我视频中的错误。因为本人不是专职老师,所以群内不会有任何的其他和NestJS无关的课程推销,只为学习NestJS交流学习使用。 当然你不加群也完全可以学会,你只看视频就可以。加群的目的只是在你学习过程中遇到问题后,有小伙伴可以帮助你解决问题,增加学习效率。
NestJS的优缺点
学新框架,需要弄清楚这个框架的优缺点。
-
代码架构合理,装饰器语法,概念比较多;
-
TS原生支持,体验好,项目的代码质量高;
-
学习坡度比较高,上手有难度(Koa/Express/Egg.js)
所以说,如果你还没有TypeScript的开发经验,我录制过一套TypeScript教程,内容并不多,最快四个小时就可以让你快速入门.
这个教程目前已经被学习23万次,并获得一致好评。如果你说我还不会TypeScript,可以拿出3、4个小时去免费学习一下。
视频更新频率
本套视频我准备每周更新3-5集,也就是以周为单位进行更新。如果公司不加班开发的话,尽量保持每天一集,当然周六、周日就不在更新了,陪陪家人。目前入门视频规划总集数在30集左右。 等快速入门结束后,会继续录制一些后端经常开发的模块,也算是开发实战的练习,最主要的是这套视频是完全免费的。 好了,如果你正好想学习,也欢迎你能扫码进群和技术胖一起学习NestJS框架。下节课正式开始走进NestJS的世界。# 02.NestJS环境搭建和项目创建
02.NestJS环境搭建和项目创建
安装Node软件
作为一个前端程序员,应该都对Node.js有所了解,如果你用过Vue
或者React
框架进行开发需求,那你的电脑上一定已经安装了Node。但为了照顾所有同学,我需要再讲下Node的安装,如果你很熟悉了,可以跳过这部分,直接到NestJS的安装讲解进行学习。 Node.js是什么?
Node.js是一个基于Chrome V8 引擎的 JavaScript运行环境。
简单理解就是Node.js为JavaScript代码的运行,提供了必要的环境。 下载方法
Node.js的官方地址:https://nodejs.org
我们直接登录网站,进行下载,我这里下载的是18.12.1LTS
版本。下载完成后,安装就和安装QQ差不多,下一步、下一步就可以完成。视频中会有展示。 验证安装是否成功 当安装完成Node后,需要验证它是否安装成功。打开命令行工具,然后在命令行中输入`node -v`,如果能正常出现版本,说明Node已经安装成功了。
node -v
v18.12.1
包管理器的使用npm&yarn&pnpm
当你下载了Node.js ,就默认提供了npm包管理工具。但是由于国内的一些因素,你使用npm来下载包是非常慢的,所以就需要我们作一些额外的事情。 npm安装淘宝源 npm慢是可以解决的,我们可以直接使用淘宝源。
淘宝源网址:https://npmmirror.com
在命令行中输入下面的命令
$ npm install -g cnpm --registry=https://registry.npmmirror.com
yarn的介绍和安装 由于npm下载慢的问题,很多人开始使用yarn这个工具。
yarn的特点是 扁平化依赖,并行安装,本地缓存。
安装方法是在命令行下,直接输入下面的命令,就可以安装成功了
// 安装命令
npm i -g yarn
// 国内加速,设置国内镜像
yarn config set registry https://registry.npmmirror.com
pnpm的介绍和安装 最近两年比较流行的包管理工具pnpm,
pnpm的特点是节约磁盘空间,缓存技术加持,速度快,安全性高,支持monorepo
安装方法
npm i -g pnpm
这三个工具是有些区别的,pnpm最快,但是对很多项目支持的不是很好,出现问题很难解决。yarn虽然现在越来越多的使用,但是当你项目依赖包很多的时候,它和npm有一些差异,会导致项目无法运行。所以说还是npm最稳定,你可以通过可以上网的方式,来解决慢的问题。
如果你说你不会科学上网,可以观看第一节,扫码入群,我在群里教你作这个。
全局安装NestJS CLI
当有Node环境后,也设置npm的源后,就可以正式进行NestJS的学习了。NestJS的安装需要CLI命令,先来安装NestJS CLI工具。
cnpm i -g @nestjs/cli
如果使用npm 进行安装,速度可能会很慢。
创建NestJS项目
等待nestjs/cli
安装完成后,就可以创建一个新的项目。
nest new nestjs-demo
打开命令行工具,然后进到你想创建项目的文件夹,输入上面的命令就可以了。输入完成后,会让我们选择使用什么包管理工具进行下载,我这里使用npm。 我家里的网速安装需要3分钟左右,这个根据个人网速不同,会有一定的差异。 安装完成后会出现下面的提示。 这时候再使用命令行进入到项目
cd nest-demo
,然后再使用npm run start
启动项目,看到下面的提示,说明我们的项目已经创建成功了。
> nest start
[Nest] 14312 - 2022/11/24 09:28:10 LOG [NestFactory] Starting Nest application...
[Nest] 14312 - 2022/11/24 09:28:10 LOG [InstanceLoader] AppModule dependencies initialized +30ms
[Nest] 14312 - 2022/11/24 09:28:10 LOG [RoutesResolver] AppController {/}: +6ms[Nest] 14312 - 2022/11/24 09:28:10 LOG [RouterExplorer] Mapped {/, GET} route +2ms
[Nest] 14312 - 2022/11/24 09:28:10 LOG [NestApplication] Nest application successfully started +2ms
好了,这节就先到这里了。学完这节后,一定要动手操作起来,把环境和项目都搭建好,下节课我们才能继续学习。
03.初始化目录介绍和HelloWorld
上节课我们使用NestJS CLI ,在D盘
根目录,创建了一个nestjs-demo
的新项目,这节主要任务是带大家看一下初始化项目的目录结构是怎么样的,每个文件的作用是什么。然后给大家展示一下HelloWorld的页面。
初始化目录文件说明
先来看一下用NestJS CLI 工具生成项目后有哪些文件。我们要了解文件,知道每个文件的作用。这对以后编写项目和看别人的开源项目非常有帮助。
+-- dist[目录] // 编译后的目录,用于预览项目
+-- node_modules[目录] // 项目使用的包目录,开发使用和上线使用的都在里边
+-- src[目录] // 源文件/代码,程序员主要编写的目录
| +-- app.controller.spec.ts // 对于基本控制器的单元测试样例
| +-- app.controller.ts // 控制器文件,可以简单理解为路由文件
| +-- app.module.ts // 模块文件,在NestJS世界里主要操作的就是模块
| +-- app.service.ts // 服务文件,提供的服务文件,业务逻辑编写在这里
| +-- app.main.ts // 项目的入口文件,里边包括项目的主模块和监听端口号
+-- test[目录] // 测试文件目录,对项目测试时使用的目录,比如单元测试...
| +-- app.e2e-spec.ts // e2e测试,端对端测试文件,测试流程和功能使用
| +-- jest-e2e.json // jest测试文件,jset是一款简介的JavaScript测试框架
+-- .eslintrc.js // ESlint的配置文件
+-- .gitignore // git的配置文件,用于控制哪些文件不受Git管理
+-- .prettierrc // prettier配置文件,用于美化/格式化代码的
+-- nest-cli.json // 整个项目的配置文件,这个需要根据项目进行不同的配置
+-- package-lock.json // 防止由于包不同,导致项目无法启动的配置文件,固定包版本
+-- package.json // 项目依赖包管理文件和Script文件,比如如何启动项目的命令
+-- README.md // 对项目的描述文件,markdown语法
+-- tsconfig.build.json // TypeScript语法构建时的配置文件
+-- tsconfig.json // TypeScript的配置文件,控制TypeScript编译器的一些行为
src目录下的文件说明 src目录是日常工作编写代码的主要目录,从基本的目录结构也可以对NestJS编写模式有很好的了解。
+-- src[目录] // 源文件/代码,程序员主要编写的目录
| +-- app.controller.spec.ts // 对于基本控制器的单元测试样例
| +-- app.controller.ts // 控制器文件,可以简单理解为路由文件
| +-- app.module.ts // 模块文件,在NestJS世界里主要操作的就是模块
| +-- app.service.ts // 服务文件,提供的服务文件,业务逻辑编写在这里
| +-- app.main.ts // 项目的入口文件,里边包括项目的主模块和监听端口号
三种项目启动脚本说明
当掌握了NestJS的基本目录和文件作用后,还需要对启动命令进行了解。打开package.json
文件,可以看到下面的代码。
"start": "nest start", // 最常用的开始模式
"start:dev": "nest start --watch", // 开发模式的启动 有监视功能
"start:debug": "nest start --debug --watch", // 调试Bug时的启动 调试程序时使用
对启动脚本了解后,我们启动一下项目。打开终端(ctrl+shift+`), 输入npm run start:dev
启动项目。
HelloWorld程序的编写
当项目启动后,我们如何访问到这个项目那?打开/src/main.ts
文件,可以看到监听的是3000
端口,这时候在浏览器地址栏中输入http://localhost:3000
就可以访问HelloWorld的程序了。 你也可以打开/src/app.service.ts
文件,把里边的Hello World
改为hello JSPang
,然后再刷新浏览器看看结果。
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello JSPang!';
}
}
好了,这就是本节课的内容了。本节主要讲解了NestJS CLI生成项目的初始目录的结构和每个文件的作用,也简单演示了一下如何访问程序和修改HelloWorld页面。希望小伙伴们都动起来,和我一起学习。
04.Controller控制器-路由的创建
上一节介绍目录的时候我说过/src/app.controller.ts
是控制器,其实也就是编写路由。
新建一个路由/Controller
比如要新建一个http://localhost:3000/jspang
的页面,访问时直接返回Hello NestJS
,这时候就可以在app.controller.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();
}
//---------添加部分-----------
@Get('JSpang')
getJSpang():string{
return 'Hello NestJS.'
}
//---------------------------
}
编写完成后,就可以来到浏览器访问http://localhost:3000/jspang ,这时候你就可以看到你想要的结果了。
增加一个顶层路径
比如现在要在所有的路由前面加一个api
的顶层路径,例如所有的路径形式变成http://localhost:3000/api/xxxxx
这个时候要如何编写那?其实方法也很简单,就是在最上层的@Controller( )
加入一个api
的字符串就可以了。
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('api')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
这时候再次访问路径就变成了http://localhost:3000/api
。
创建一个模块和路由
上面这个路由是用NestJS CLI 自动给我们生成的,只是作一个参考,让我们可以看到一个页面。我们真实项目一般不是这样的,都是一个页面或者一个大的模块建立一个文件夹。在这个文件夹里有不同的controller、service和module。下面我就模拟真实中的开发,给大家创建一个Girl
的模块,来讲述Controller
的设置。 首先删除自动生成的测试文件app.controller.spec.ts
、控制文件app.controller.ts
和app.service.ts
都删除掉。 然后进入到`app.modlue.ts`文件中,把刚才删除内容的引用,再次删除掉,删除后的文件如下。 删除前的app.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 {}
删除后的app.module.ts文件
import { Module } from '@nestjs/common';
@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule {}
当删除都完成后,我们就可以新建模块了。NestJS 鼓励使用终端的形式新建模块,打开终端[Ctrl+Shift+`]。输入下面的命令,新建一个模块。
nest g module girl
稍等一会,在/src
目录下,就自动建立了一个girl
的文件夹,在文件夹里还自动生成了girl.module.ts
的。 再返回/src
目录,可以看到在app.module.ts
文件里,给我们自动引入了刚刚创建的模块。
import { Module } from '@nestjs/common';
import { GirlModule } from './girl/girl.module';
@Module({
imports: [GirlModule],
controllers: [],
providers: [],
})
export class AppModule {}
也就是说,我们用这种命令行创建的文件,NestJS CLI会自动为我们进行导入,比如现在我们再创建一个`controller`.
nest g controller girl --no-spec
稍等一会,它自动为我们在/src/girl
目录下,创建了girl.controller.ts
文件。有了这个文件以后,我们就可以在这个文件里创建路由了。编写下面的代码:
import { Controller, Get } from '@nestjs/common';
@Controller('girl')
export class GirlController {
@Get()
getGirls():any{
return{
code:0,
data:['翠花','小红','大丫'],
msg:'请求女孩列表成功'
}
}
}
编写完成后,在浏览器中打开http://localhost:3000/girl
就可以看到返回的信息了。 我们真实的开发更倾向于这种模式。
在main.ts设置默认前缀
这时候想在所有的请求加入前缀/honglangman
,就可以到/src/main.ts
里,编写下面的代码。
app.setGlobalPrefix('HongLangMan');
这时候的访问地址就变成了http://localhost:3000/honglangman/girl
。 好了,这节文章就到这里,我相信学完后你对NestJS里Controller的创建已经有了新的了解。下节课我们学习一下如何创建一个Service,然后如何在Service里编写业务逻辑。
05.Controller控制器-业务逻辑相结合
继续上节课的内容,这节是上节的补充。上节课新建了girl
模块,然后在girl
模块下面新建了girl.controller.ts
控制器,并返回了一个JSON
对象。但现在有个问题,按NestJS规定是不应该在controller
里写任何的业务逻辑,而是把业务逻辑放在girl.service.ts
这个文件中。
用命令行创建service
同样使用终端命令行的形式创建girl.service.ts
文件,在VSCode中打开终端,输入下面的命令。
nest g service girl --no-spec
稍等一会,就会给我们两句提示。
CREATE src/girl/girl.service.ts (88 bytes)
UPDATE src/girl/girl.module.ts (240 bytes)
看到这个就说明我们已经创建成功了。第一句是告诉我们girl.service.ts
文件创建成功了,第二句话是告诉自动更新girl.module.ts
文件。
Controller(控制器)引入service(逻辑)
service的文件属于逻辑层,按照约束,需要我们把业务逻辑相关的东西都写到这个文件里。当service文件创建好以后,我们需要作的第一件事,就是把service引入到controller控制器里。打开girl.controller.ts
文件,编写下面的代码。
import { Controller, Get } from '@nestjs/common';
import { GirlService } from './girl.service';
@Controller('girl')
export class GirlController {
constructor(private girlService:GirlService){}
//this.girlService = new GirlService();
//.......
}
在service里编写业务逻辑
在控制器里引入service后,就可以在service里编写业务逻辑了。比如我们把返回的代码移入girl.service.ts
,代码如下。
import { Injectable } from '@nestjs/common';
@Injectable()
export class GirlService {
getGirls(){
return{
code:0,
data:['翠花','小红','大丫'],
msg:'请求女孩列表成功'
};
}
}
这里的代码就相当于业务逻辑了,写完后,还需要回到girl.controller.ts
里,在getGirls( )
方法里使用service里的getGirls( )
方法。
import { Controller, Get } from '@nestjs/common';
import { GirlService } from './girl.service';
@Controller('girl')
export class GirlController {
constructor(private girlService:GirlService){}
@Get()
getGirls():any{
return this.girlService.getGirls();
}
}
写完上面这句代码后,就可以在浏览器中http://localhost:3000/girl
进行访问。如果一切正常,依然可以看到上节课访问时一模一样的内容,只不过这时候我们的业务逻辑,已经挪到了Service
中。上面这些就是我们在创建一个基本Moudule
后,要作的基本操作。 下节课继续讲解Controller相关的内容,会讲一下如何创建POST路由,如何传递参数,这些内容。
06.Controller控制器-Get和Post请求详解
这节我们主要学习在Controller里设置POST请求和如何获得请求中的参数,为了更好的演示,我们还会演示在VSCode上用REST Client来发送请求,调试接口的操作。
创建POST请求
用VSCode打开/src/girl/girl.service.ts
,先编写一个增加女孩的方法addGirl( )
。 代码编写如下:
addGirl(){
return {
code:200,
data:{id:1,name:'大梨',age:27},
msg:'女孩添加成功'
};
}
这个是业务逻辑的编写,写完这个后,我们再回到girl.controller.ts
, 先引入Post
。
import { Controller, Get,Post } from '@nestjs/common';
引入完成后,再用装饰器编写一个@Post
请求方法。
import { Controller, Get,Post } from '@nestjs/common';
import { GirlService } from './girl.service';
@Controller('girl')
export class GirlController {
constructor(private girlService:GirlService){}
//this.girlService = new GirlService();
@Get()
getGirls():any{
return this.girlService.getGirls();
}
//------新增Post请求方法
@Post('/add')
addGirl():any{
return this.girlService.addGirl();
}
}
这样就创建好了,我们可以使用REST Client 进行Post请求。
REST Client 的使用
REST Client
是VSCode的一个用于发送请求的插件,在以前的视频中我也讲过,小伙伴可以去考古一下,我这里给出了以前的视频地址。
当插件安装完成后,就可以在项目根目录下创建一个`/RESTClient/demo.http`的文件,然后编写下面的代码。
POST http://localhost:3000/girl/add HTTP/1.1
点击Send Request
按钮,发送请求,如果一切正常,我们就可以看到请求返回的结果了。
接受Get请求参数
在开发中,我们的请求一般都是带有参数的,比如在女孩中根据ID出现相对的女孩。这时候我们传递的就是ID,得到的是对应女孩的信息。
先到/src/girl/girl.service.ts
文件里编写对应的业务逻辑。
getGirlById(id:number){
let reJson:any ={}
switch(id){
case 1:
reJson={id:1,name:'翠花',age:18}
break;
case 2:
reJson={id:1,name:'小红',age:20}
break;
case 3:
reJson={id:1,name:'大丫',age:23}
break;
}
return reJson;
}
写完业务逻辑后,我们到控制器页面`/scr/girl/girl.controller.ts`,用 @Get
装饰器编写一个路由,在代码最开始的地方我们引入了`Request`。
import { Controller, Get,Post ,Request } from '@nestjs/common';
Request的作用就是获得参数,当有了Request后,我们继续编写对应的`getGirlById( )`方法。
@Get('/getGirlById')
getGirlById(@Request() req):any{
//因为通过Get方式传递过来的是字符串,所有我们需要用parseInt转化为数字
let id:number = parseInt(req.query.id)
return this.girlService.getGirlById(id)
}
这些编写完成后,我们再到`demo.http`文件里,作一个发送请求。
Get http://localhost:3000/girl/getGirlById
?id=1
然后点击Send Request
按钮后,就可以根绝id不同,等到不同的结果了。
@Query装饰器的使用
上面的req.query.id
写起来不简介,我们可以使用@Query
修饰器来进行简写。现在文件最开始引入它。
import { Controller, Get,Post ,Request,Query } from '@nestjs/common';
然后把@Request
换成@Query
。
@Get('/getGirlById')
getGirlById(@Query() query):any{
let id:number = parseInt(query.id)
return this.girlService.getGirlById(id)
}
这样写也是完全没有问题的,算是一种简写方法。
接受Post请求参数
Post请求和Get基本一样,也可以使用@Request
装饰器接收就可以了。这里把本节课开始时写的addGirl( )
方法改为接受参数的方式。
@Post('/add')
addGirl(@Request() req):any{
console.log(req.body)
return this.girlService.addGirl();
}
这时候到demo.http
里,编写请求的代码,注意这里我们传递了参数。
POST http://localhost:3000/girl/add
Content-Type: application/json
{
"id":4,
"name":"大梨",
"age":30
}
点击Send Request
按钮,可以看到终端内就会显示出我们请求的数据。
@body修饰器的使用
和@Query
修饰器一样,NestJS也为我们准备了@Body
修饰器。 使用前同样需要先引入Body。
import { Controller, Get,Post ,Request,Query,Body } from '@nestjs/common';
这时候直接修改代码为下面的形式,依然可以运行。
@Post('/add')
addGirl(@Body() body):any{
console.log(body)
return this.girlService.addGirl();
}
好了,这节的视频就讲到这里,下节我们讲一下动态路由的创建和参数的接受。
07.Controller控制器-动态路由的创建
NestJS还提供动态路由的相关编写方法。其实动态路由,就是在Get请求时,把参数的传递掩饰在路径里,而不是用传统的方式进行传递。
例如我们上节课编写的通过ID获得一个女孩的的路由,请求时我们需要携带参数。
Get http://localhost:3000/girl/getGirlById
?id=1
改为动态路由时,请求路由就变成了这样。
Get http://localhost:3000/girl/getGirlById/1
创建动态路由
在Controller
里在新建一个方法findGirlById
,制作动态路由。
@Get('/findGirlById/:id')
findGirlById(@Request() req):any{
let id:number = parseInt(req.params.id)
return this.girlService.getGirlById(id)
}
写完后,我们依然使用REST Client
进行测试,这次就不需要显式的传递参数,可以隐式的以路径形式进行传递。
GET http://localhost:3000/girl/findGirlById/2
可以看到这种形式也是完全可以的。
多参数的传递
实际开发中,我们可能传递的不仅仅是一个参数,而是多个参数。NestJS也可以满足多参数的传递。
@Get('/findGirlById/:id/:name')
findGirlById(@Request() req):any{
console.log(req.params.name)
let id:number = parseInt(req.params.id)
return this.girlService.getGirlById(id)
}
我们又接收了一个新的name参数,然后打印在了控制台。这时候的访问路径就变成了,下面的形式。
GET http://localhost:3000/girl/findGirlById/2/jspang
@Param装饰器的使用
NestJS也提供了对应的简写装饰器@Param
,使用前记得先进行引入。
import { Param } from '@nestjs/common';
然后修改对应的代码为下面的形式。
@Get('/findGirlById/:id/:name')
findGirlById(@Param() params):any{
console.log(params.name)
let id:number = parseInt(params.id)
return this.girlService.getGirlById(id)
}
@Headers装饰器
如果想读取请求头里边的信息,就可以使用@Headers
装饰器,使用前还是需要引入的。
import { Headers} from '@nestjs/common';
然后修改对应的代码获得请求头信息。
@Get('/findGirlById/:id/:name')
findGirlById(@Param() params ,@Headers() header):any{
console.log(params.name)
console.log(header)
let id:number = parseInt(params.id)
return this.girlService.getGirlById(id)
}
这样就可以得到Header的信息了。
好了,这节课的知识也是非常重要的,因为我们大多数的Get路径都会使用动态路由的形式来请求。所以看完文章或者视频后,一定要动手进行编写。
08.数据库操作-NestJS中ORM工具简介
ORM 是 Object Relational Mapping 的缩写,译为“对象关系映射”,它解决了对象和关系型数据库之间的数据交互问题。
这是百度搜索ORM
的结果,只看它你可能还搞不清楚什么是ORM,特别是前端同学对这个概念就更加陌生了。但是如果是后端同学,学过Java和J2EE对这个一定有所了解,因为在学Java的时候一定会学一个工具 Hibernate
,它就是典型的ORM工具。
简单理解ORM
你可以简单理解ORM的作用就是:定义一个对象,这个对象就对应着一张表,这个对象的一个实例,就对应着表中的一条记录。 这样作的好处,就是我们可以通过对象的形式,来操作数据库。比如取表中的一个字段,那应该就对应对象中的一个属性。对象的形式,对于前端工程师来说非常友好。 传统的SQL
SELECT id,name,age FROM girls where id=1
// 代码
res = db.execSql(sql)
name=res[0]['NAME']
ORM代码
girl= Girl.get(1)
name=girl.name
用ORM的形式,就不用再写SQL语句了,因为ORM工具就已经为我们做好了。这样我们的学习成本就相对来说降低了,而且也提高了阅读性。
ORM的优缺点
当我们知道了ORM是什么工具之后,还需要了解它的优缺点。 这里先说优点:
-
方便维护:数据模型定义在同一个地方,利于重构;
-
代码量少、对接多种库:代码逻辑更易懂
-
工具多、自动化能力强:数据库删除关联数据、事务操作等....
再来说缺点:
-
性能没有使用传统SQL语句效率高,因为SQL语句的形式是可以进行优化的,而由于ORM工具这种高度集成化的形式,所以没办法进行SQL语句的优化。所以在一些极端情况下会出现性能问题。
常见的ORM工具
-
prisma : 个人推荐使用的ORM工具,由于是公司维护,所以个人感觉稳定性更好。
-
TypeOrm:官方推荐使用的ORM,社区维护,性能略由于prisma工具。
这套视频前期,我们会主要讲解TypeOrm,因为这是NestJS官方推荐使用的工具。其实你不用纠结使用哪个,这两个工具使用非常类似,学会一种再上手另一种也是非常容易的。
09.数据库操作-安装MySql 和TypeORM
当我们知道什么是ORM工具后,我们就要动手操作了。拿国内用的较多的MySql数据库为例,讲解一下MySql数据库的安装和基本操作方法,然后再安装和配置一下TypeORM。由于不是专门讲MySql的教程,所以会采用既简单的方式来进行操作,目的是能让小伙伴快速上手操作,然后做出视频中的案例就可以。
所以你如果有数据库的一些开发经验,可以按照你自己的方式进行安装操作,不用纠结本节课的操作方式和做法。
安装MySQL数据库
我们这里采用集成开发环境,PhpStudy(小皮面板)直接安装,因为它的安装最简单,操作最方便。
官方网址:https://www.xp.cn/
下载之后安装非常方便,就和安装QQ一样了。安装完成就可以看到操作面板了(此部分的操作在视频中演示)。
Database Client 插件的安装
当安装了数据库之后,使用SQL语句来查看数据库的数据和结构,效率太低了。我们需要一款软件或工具来完成这部分的操作。我的个人癖好是能在VSCode里完成的操作,就不再打开其他软件了。
我目前使用的数据库管理插件叫做Database Client
,直接在VSCode的插件中查找安装就可以使用了。 使用方法,我在视频中进行演示了,这里就不作过多介绍了。
安装TypeORM
上节中说了,TypeORM是NestJS官方推荐的工具库,所以我们也先来讲解TypeORM。我们采用命令行的方式进行安装。
npm install --save @nestjs/typeorm typeorm mysql2
这个安装会根据你网速的快慢有所不同。安装完TypeORM之后,就可以编写代码,作一个连接了。
引入TypeORM
我们直接再/src/app.module.ts
中引入typeorm
。
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports:[ TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
username:'root', // 连接账号
password:'root123', // 连接密码
database:'test_db', // 连接的表名
retryDelay:500, // 重试连接数据库间隔
retryAttempts:10, // 允许重连次数
})],
controllers: [GirlController],
providers: [GirlService],
})
编写完成后在控制台启动服务.
npm run start:dev
访问http://localhost:3000/girl
如果不报错,说明我们的数据库已经连接成功了。 好了,这节就先到这里了,下节我们讲述TypeORM中实体的概念。这节学完之后,一定要进行操作,如果不跟着作,下节你就没办法跟着进行继续学习了。
10.数据库操作-TypeORM的实体操作
通过上节的学习,你已经安装好了数据库和相关的TypeORM工具,这节课我们就学习一下TypeORM重的实体概念和操作。
TypeORM中的实体Entities
ORM中的实体其实就是把数据库映射成对象的那个类。这个类可以模拟数据库表,定义其中的字段。
因为映射的过程ORM已经为我们作好了,所以我们只需要定义实体类,当实体类定义好以后,就可操作数据库了。下面的图在第八节中已经见过,这里所谓的实体,就是Object
。 有了这个Object就可以自动生成表结构,而且DB里的数据也编程了标准的对象。
增加配置项
如果想让TypeORM自动工作,为我们创建对应的数据库结构,我们需要先在配置数据库连接的地方增加两个配置项,一个是允许实体同步到数据库,另一个是自动加载实体。
import { Module } from '@nestjs/common';
import { GirlModule } from './girl/girl.module';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [GirlModule, TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
username:'root', // 连接账号
password:'root123', // 连接密码
database:'nest_demo', // 连接的表名
retryDelay:500, // 重试连接数据库间隔
retryAttempts:10, // 充实次数
synchronize:true, // 是否将实体同步到数据库
autoLoadEntities:true, // 自动加载实体配置,forFeature()注册的每个实体都自己动加载
})],
controllers: [],
providers: [],
})
export class AppModule {}
编写Entities实体
你已经知道了什么是TypeORM中的实体了,我们也作好了配置,接下来就可以编写一个实体。先进入到/src/girl
目录下,新建一个文件夹entities
,然后在里边新建girl.entity.ts
编写实体。
import {Entity , Column ,PrimaryGeneratedColumn} from 'typeorm'
@Entity()
export class Girl{
@PrimaryGeneratedColumn()
id:number
@Column()
name:string
@Column()
age:number
@Column()
skill:string
}
编写好类之后,需要在module
里进行引入,打开girl.module.ts
文件,先用import
引入TypeOrmModule
。
import {TypeOrmModule} from '@nestjs/typeorm'
然后在@Module( )
装饰器中用forFeature( )
方法,把Girl
的实体引入。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
.....
})
写完后启动数据库服务,数据服务启动好以后,再控制台启动NestJS服务。
npm run start:dev
两个服务都没有问题后,我们到VSCode中的Database Client
中,查看数据库是否已经创建了`girl`的表。
实体字段的详细介绍
列类型:可以使用type
字段定义列的类型和长度。
import {Entity , Column ,PrimaryGeneratedColumn} from 'typeorm'
@Entity()
export class Girl{
@PrimaryGeneratedColumn("uuid")
id:number
@Column({type:"varchar",length:255})
name:string
@Column({type:"int"})
age:number
@Column({type:"varchar"})
skill:string
}
这里给出目前所有的MySql类型,你可以收藏一下这篇文章,需要的 时候来查询。说时候我也是记不住这些类型的。
int, tinyint, smallint, mediumint, bigint, float, double, dec, decimal,
numeric, date, datetime, timestamp, time, year, char, varchar, nvarchar,
text, tinytext, mediumtext, blob, longtext, tinyblob, mediumblob, longblob,
enum, json, binary, geometry, point, linestring, polygon, multipoint,
multilinestring, multipolygon, geometrycollection
自动生成时间 在开发中经常需要在保存数据时同时记录时间,这时候可以使用@CreateDateColumn( )
装饰器来制作。我们先进行引入。
import {CreateDateColumn} from 'typeorm'
引入后就可以使用@CreateDateColumn( )
装饰器来定义字段了。
@CreateDateColumn({type:"timestamp"})
entryTime:Date
自动生成列 开发时也会遇到生成一个不规则、不重复的自动编号,这时候就可以使用Generated( )
装饰器。
import {....,Generated} from 'typeorm'
@Generated('uuid')
uuid:string
其实关于TypeORM的实体配置还有很多,但是我们没有必要全部记住,用的时候查就可以了。这节你只要能知道什么是实体,能定义出想要的实体类就可以了。
11.数据库操作-ORM对数据库的增删改查
当我们有了一个Girl的实体(Entity)之后,我们就可以对一个表的数据进行增删改查了。操作数据库的数据,也是后端开发的一个核心技能。
引入必要的装饰器和工具
对数据的操作,我们一般都放在xxx.service.ts
这个文件里,因为算是业务逻辑的编写。这里我们就放在girl.service.ts
文件里。打开文件,我们还需要引入三个必要的东西。
import {Like, Repository} from 'typeorm'
import {InjectRepository}from '@nestjs/typeorm'
import {Girl} from './entities/girl.entity'
引入后我们要在构造函数里增加一个依赖注入的操作。
// 依赖注入
constructor(@InjectRepository(Girl) private readonly girl:Repository<Girl>){}
上面的两步完成,我们才能进行正式的增删改查。
增加数据save( )
先到girl.service.ts
文件里,来编写增加数据的方法addGirl( )
。这里我们先new
出一个girl
的对象,然后用这个对象的save( )
保存数据就可以了。
//增加一个女孩
addGirl(){
const data = new Girl()
data.name='大梨';
data.age=25;
data.skill='精油按摩,日式按摩';
return this.girl.save(data);
}
有了业务逻辑,需要去girl.controller.ts
里,去配置一下对应的路由。
@Get('/add')
addGirl(@Body() body):any{
console.log(body)
return this.girlService.addGirl();
}
然后我们启动服务,访问路径,看看是否数据表girl
已经增加了一条数据。
npm run start:dev
// http://localhost:3000/girl/add
我们可以修改girl.service.ts
文件里的数据,多增加几条,方便后面的操作。
删除数据delete( )
继续在girl.service.ts
文件里编写删除方法。删除方法使用delete( )
,、
// 删除一个女孩
delGirl( id:number){
return this.girl.delete(id)
}
这里我们接收一个参数为id
,然后根据id
进行删除。 写完后还是去girl.controller.ts
里编写对应的路由。
@Get('/delete/:id')
deleteGirl(@Param() params):any{
let id:number = parseInt(params.id)
return this.girlService.delGirl(id);
}
写好后到浏览器中访问,删除ID为4的数据。
http://localhost:3000/girl/delete/4
修改数据update( )
修改数据需要根据id,再进行修改,也就是要接收两个参数,一个是id,一个是要修改的数据。
//修改一个女孩
updateGirl( id: number ){
let data = new Girl()
data.name="王小丫";
data.age=19
return this.girl.update(id,data)
}
然后再到girl.controller.ts
里,编写对应的路由。
@Get('/update/:id')
updateGirl(@Param() params):any{
let id:number = parseInt(params.id)
return this.girlService.updateGirl(id);
}
查询数据find( )
查询数据我们一般分两种,一种是查询表中的所有数据,一种是根据姓名模糊查询(或者根据ID查询)。先在girl.servie.ts
中写一个查询全部数据的。 ** 查询全部数据 **
// 得到所有女孩
getGirls(){
return this.girl.find()
}
这个是不是超级简单,一句话就可以完成。再到girl.controller.ts
里编写对应的路由。
@Get()
getGirls():any{
return this.girlService.getGirls();
}
然后访问http://localhsot:3000/girl
就可以看到全部的数据打印在了页面上。 ** 根据姓名模糊查询 ** 再来写一个根据姓名模糊查询的方法。
// 根据姓名查找一个女孩的信息
getGirlByName(name:string){
return this.girl.find({
where:{
name:Like(`%${name}%`)
}
})
}
这里使用的Like是模糊查询的意思,也就是根据姓名来模糊查询数据。写完这个继续编写对应的路由配置。
@Get('/findGirlByName/:name')
findGirlByName(@Param() params ):any{
console.log(params.name)
let name:string = params.name
return this.girlService.getGirlByName(name)
}
这样我们就完成了对一个数据表的增删改查,当然这只是最简单的操作,真实的开发中要比这个复杂很多。
12. Providers(提供者)实现依赖注入
上节对数据库的增删改查中,我们讲解了依赖注入,群里的小伙伴有对这个概念比较陌生的,所以这节课就专门讲一下,NestJS 中的依赖注入。
先来了解一个单词,Providers
可以翻译成提供商,或者提供者。有了这个工具的帮助,我们就可以在constructor
(构造函数)里注入依赖关系。有了Providers
表示对象可以彼此形成各种关系,更好更灵活的组建开发。
初识 Providers 提供者
上面的文字对小伙伴的理解不会有太大帮助,我这里画一幅图,来帮助大家理解。
图示:图的左边是
service
文件,也就是我们 常说的服务类,数据库和业务逻辑的操作代码就放在这个里面。图的右边是controller
文件,我们常说的路由文件,但是路由文件最终要调用service
文件里的方法,而此时的调用是通过this.girlService.addGirl()
完成的。能使用这种形式的功劳就是有Providers
提供者的帮忙,也就是我们常说的依赖注入IOC
. 其实使用依赖最多的框架是 Java Spring boot 框架。 NestJS 也借鉴了 SpringBoot 框架,在自己的框架里边大量的使用了依赖注入这种写法。
Providers 自定义名称
打开girl.module.ts
文件,可以看到下面这样的代码。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
controllers: [GirlController],
providers: [GirlService],
})
这里的providers: [GirlService],
只是一种简写,他的正规写法应该是这样。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
controllers: [GirlController],
providers: [{
provide:"girl",
useClass:GirlService
}],
})
但这时候我们的程序会报错,因为在girl.controller.ts
里没有进行注入的操作,简写是不用编写注入操作的,但如果自定义名称,就需要写一下,先引入Inject
装饰器。
import { Controller, Inject, Get, Body, Param } from "@nestjs/common";
然后在constructor
使用注入装饰器,并把名字作为参数传递过去。
constructor(@Inject('girl') private girlService:GirlService){}
自定义注入值
我们不仅仅可以注入service
类中的方法,我们还可以定义一些数据值,直接注入到controller
里,直接在girl.module.ts
里编写代码,定义一个数组。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
controllers: [GirlController],
providers: [{
provide:"girl",
useClass:GirlService
},{
provide:"GirlArray",
useValue:['小红','小翠','大鸭']
}],
})
然后还需要到girl.controller.ts
进行注入操作。
constructor(
@Inject('girl') private girlService:GirlService,
@Inject('GirlArray') private girls:string[],
){}
写完注入后,我们就可以在方法中进行使用了,这里我们新写一个路由方法test
@Get('/test')
test():string[]{
return this.girls;
}
}
然后在浏览器中访问路径,就可以看到刚才定义的数组了。
http://localhost:3000/girl/test
自定义工厂(方法)
有时候我们要注入一些除service
方法之外业务逻辑,就可以使用这种工厂模式。 先在girl.module.ts
文件里编写下面的工厂方法。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
controllers: [GirlController],
providers: [
......
{
provide:"MyFactory",
useFactory(){
console.log('myFactory---------:')
return 'console.log() function'
}
}],
})
然后到girl.controller.ts
里边,进行注入。
constructor(
.......
@Inject('MyFactory') private myFactory:string,
){}
注入完成后,我们就可以在test( )
方法里使用它了。
@Get('/test')
test():string[]{
console.log(this.myFactory)
return this.girls;
}
其实在这个方法中可以作很多事情,甚至写一些必要的业务逻辑,现在我们只是用了最简单的方式来编写。
这节课你学习的目的,也只需要了解到什么是依赖注入,什么是 Provider 提供者,已经提供者提供的可注入的三种形式就可以了。
13. 为NestJS增加热重载功能
NestJS并没有提供热重载,随着学习的深入,每次修改文件保存,服务都需要经过几秒的重新编译。在现实中的项目开发,这个过程会变成十几秒到几十秒,非常影响开发的连贯性。所以这节课为我们就修改webpack配置,为NestJS增加热重载功能。
热重载的作用
热重载会对比代码变化,然后只重新编译变化的文件,而不是所有的文件都重新编译。
需要说明的是,配置热重载只适合在项目初期,就是开发阶段。如果你项目进入维护阶段,建议去掉热重载功能。因为它对实体(Entities)和静态文件支持的不是很好。官方也是因为没办法完美的解决一些问题,所以没有加入热重载功能。
配置热重载
1.安装依赖包 配置热重载首先要安装相关的依赖,这里直接使用npm进行安装
$ npm i --save-dev webpack-node-externals run-script-webpack-plugin webpack
你可以直接复制上面这段代码,我是记不住的,又长又难记。
2.增加配置文件 安装完成后,我们需要在项目根目录下增加一个webpack-hmr.config.js
的配置文件。有了文件后,再把下面的代码复制到文件里。
const nodeExternals = require('webpack-node-externals');
const { RunScriptWebpackPlugin } = require('run-script-webpack-plugin');
module.exports = function (options, webpack) {
return {
...options,
entry: ['webpack/hot/poll?100', options.entry],
externals: [
nodeExternals({
allowlist: ['webpack/hot/poll?100'],
}),
],
plugins: [
...options.plugins,
new webpack.HotModuleReplacementPlugin(),
new webpack.WatchIgnorePlugin({
paths: [/\.js$/, /\.d\.ts$/],
}),
new RunScriptWebpackPlugin({ name: options.output.filename, autoRestart: false }),
],
};
};
3.修改对应代码 有了配置文件后,还需要手段增加一段新代码,让项目支持热加载,这段代码是增加到/src/main.ts
文件下面的。
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
增加后,文件会报hot
不存在的错误,所以我们需要安装一个新的依赖包,使用npm进行安装。
$ npm i -D @types/webpack-env
安装完成,再回来看main.ts
文件,发现错误已经消失了。 4.替换启动脚本 上面的配置完成后,我们需要替换package.json
中的start:dev
脚本。
"start:dev": "nest build --webpack --webpackPath webpack-hmr.config.js --watch",
这样配置完成后,我们就可以使用热加载功能了。
打开项目终端,然后输入npm run start:dev
启动项目。然后在/src/girl/girl.controller.ts
文件里增加一个hotLoad( )
方法。
@Get('/hotLoad')
hotLoad():any{
return 'HotLoad Function';
}
这时候我们再保存,你会发现NestJS没有重新编译所有文件,而只是编译了改变的文件,也就是实现了热加载。
有的小伙伴可能会有些疑问,因为感觉没有什么效率上的提升,现在我们是项目太小,而且没有什么业务逻辑。工作中项目复杂起来,几万行代码,你就可以看到这种热加载的效率提升了。
14.中间件Middleware-局部中间件和全局中间件
无论是前端和后端对中间件这个概念都不陌生,NestJS当然也是支持中间件的编写的。有了中间件就可以通过编写中间件的形式,实现一些资源共享,功能共享的目的。比如你作一个全站的访问计数功能,就可以使用中间件来实现,比如我们常用的访问日志功能,都可以在中间件里实现。
生成局部中间件
NestJS的局部中间件也需要使用命令行来生成,生成的命令如下。
nest g mi counter
这里我们生成一个计数器的中间件。命令执行完以后,会在/src
目录下,生成一个counter
文件夹。 我们打开counter.middleware.ts
文件,可以看到下面的代码。
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class CounterMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
next();
}
}
为了能看到效果,我们在next( )
代码上面,添加一句输出。
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class CounterMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('进入中间件')
next();
}
}
这样每次进入中间件的代码,我们都可以看到结果.这里说一下Next( )
方法的作用,如果不写这个方法,那程序执行到这里就会停止,不会向下执行后面的代码了.
局部中间件的使用
中间件可以分为局部中间件和全局中间件,我们先讲局部中间件,也就是应用中的某些路径会进入中间件。
中间件写好以后,还要在module
文件里写一些代码,让她生效.这里就在girl.module.ts
这个文件里使用中间件.我们先引入中间件.
import {CounterMiddleware} from '../counter/counter.middleware'
引入后,在GirlModule
的类上,实现NestModule
接口.具体代码如下.
export class GirlModule implements NestModule{
configure(consumer: MiddlewareConsumer) {
consumer.apply(CounterMiddleware).forRoutes('girl')
}
}
这样我们的中间件就可以使用了,启动服务.npm run start:dev
,然后访问girl相关的路由,就可以看到控制台输出了进入中间件
这样的提示.说明我们的中间件已经起作用了.
全局中间件的使用
明白了局部中间件的使用,我们再学习一下全局中间件顾名思义,就是应用中的每个路径都会先进入中间件。注意局部中间件我们是以实现NestModule
接口的形式,把中间件挂在到Moudle
上的.而全局中间件要以function
的形式 编写到main.ts
里。 先来编写一个全局中间件的方法.
function MiddleWareAll(req:any,res:any,next:any){
console.log('我是全局中间件.....')
next()
}
然后再下面的bootstrap( )
方法中进行使用.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(MiddleWareAll)
await app.listen(3000);
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
}
这样就完成了全局中间件的编写.
这节课我们就先到这里,下节课我们会讲讲第三方中间件的使用和一些补充的小知识点.下节见。
15.中间件Middleware-用第三方中间件实现跨域
接着上节课继续学习中间件相关的知识,这节课我们看看第三方中间如何使用,学完后我们再补充一些中间件相关的零碎小知识。
测试跨域请求
做过前端的同学,一定都知道在对接接口的时候,会遇到跨域问题。我们一般会让后端解决这个问题,因为前端解决还是挺麻烦的。我们就借着第三方中间件的机会,讲一下使用cors
实现后端跨域返回。 需要说的是,我们这里只是为了讲课,其实NestJS是自带配置项的,可以通过简单的配置完成跨域操作。 先编写一下corsTest( )
路由方法,打开/src/girl/girl.controller.ts
文件,然后编写下面的方法。
@Get('/corstest')
corsTest():object{
return {message:'测试跨域请求成功'}
}
我们这里使用Node环境来实现跨域,直接打开浏览器,登录我的博客https://jspang.com
,然后按F12
打开控制台,输入下面的代码。
fetch('http://localhost:3000/girl/corstest')
.then(res=>res.json())
.then(res=>{console.log(res)})
可以看到,直接报错误,说明现在程序是支持跨域请求的。
使用第三方插件解决跨域
使用第三方中间件cors
来解决这个问题,我们需要先使用npm来进行安装,一个是插件本身,一个是声明插件。
$ npm install cors
$ npm install @types/cors -D
安装完成后,就可以使用了。打开main.ts
文件编写全局的跨域代码。 先用import
进行引入cors
.
import * as cors from 'cors'
然后使用use
进行使用,注意这个中间件的使用是有顺序的,如果顺序错了,是会报错的。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cors from 'cors'
function MiddleWareAll(req:any,res:any,next:any){
console.log('我是全局中间件.....')
next()
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cors())
app.use(MiddleWareAll)
await app.listen(3000);
if (module.hot) {
module.hot.accept();
module.hot.dispose(() => app.close());
}
}
bootstrap();
报错后,这时候再次到浏览器中测试跨域,可以发现已经可以完成跨域操作了。
关于跨域其他知识的补充
send( )方法的使用 在学习局部中间件的时候,我们学了next( )
方法,是向下执行,但有时候我们并不希望程序继续执行,比如发现了非法访问路径,这时候我们就需要使用res.send( )
方法进行拦截。 打开/src/counter/counter.minddleware.ts
文件,修改代码如下。
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class CounterMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('进入中间件')
res.send('禁止访问,你被拦截了');
//next();
}
}
然后再进行访问,可以看到girl.controller.ts
里的代码这次并没有执行,而是直接返回了禁止访问,你被拦截了
的文字。
指定请求类型
还可以设置请求的类型,比如Get请求通过中间件,Post请求不通过中间件。打开/src/girl/girl.module.ts
文件,在forRoutes( )
方法中,把原来的字符串形式,改成对象形式,然后修改对象属性,就可以用来控制请求类型了。
export class GirlModule implements NestModule{
configure(consumer: MiddlewareConsumer) {
consumer.apply(CounterMiddleware).forRoutes({path:'girl',method:RequestMethod.GET})
}
}
好了,中间件我们就讲到这里了。
16.模块Module-基本使用和全局模块
其实我们早都接触过NestJS的模块,其实模块就是xxx.module.ts
文件。一个NestJS应用至少有一个根模块。但是在实际开发中我们并不会只有一个根模块,而是把每个大的功能,分成一个模块。那什么是大的功能那?你可以简单理解为紧密相连的相关功能,都可以放到一个模块中。比如对用户登录鉴权的操作,一般都会写一个独立的模块。你可以理解为把对一个数据表的增删改查都放在一个模块中。 我们这里的学习,其实主要学习@Modlue( )
装饰器的使用。
基本使用方法
我们可以使用nest g res boy
的模块,来创建一个男孩
的模块。当模块创建好以后,/src/app.module.ts
会自动引入BoyModule
模块。
import { Module } from '@nestjs/common';
import { GirlModule } from './girl/girl.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BoyModule } from './boy/boy.module';
@Module({
imports: [GirlModule, BoyModule,TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
username:'root', // 连接账号
password:'root123', // 连接密码
database:'nest_demo', // 连接的表名
retryDelay:500, // 重试连接数据库间隔
retryAttempts:10, // 充实次数
synchronize:true, // 是否将实体同步到数据库
autoLoadEntities:true, // 自动加载实体配置,forFeature()注册的每个实体都自己动加载
}) ],
controllers: [],
providers: [],
})
export class AppModule {}
而且会在/src
目录下,生成一个boy
的文件夹,这样一个模块就建立好了。
共享模块的设置
创建好的Boy
模块,只能在模块中进行调用,如果想其他模块需要调用此模块中的方法,需要进行设置。比如我们想在girl.controller.ts
中使用boy.service.ts
中的方法。 exports模块编写 先到body.module.ts
用exports
导出模块中业务逻辑。
import { Module } from '@nestjs/common';
import { BoyService } from './boy.service';
import { BoyController } from './boy.controller';
@Module({
controllers: [BoyController],
providers: [BoyService],
exports:[BoyService]
})
export class BoyModule {}
** 到module里设置注入**
然后再到/src/girl.module.ts
中进行编写。我们先引入这个service
文件。
import {BoyService} from '../boy/boy.service';
然后设置注入providers
,代码如下。
@Module({
imports:[TypeOrmModule.forFeature([Girl])],
controllers: [GirlController],
providers: [BoyService,{
provide:"girl",
useClass:GirlService
},{
provide:"GirlArray",
useValue:['小红','小翠','大鸭']
},{
provide:"MyFactory",
useFactory(){
console.log('myFactory---------:')
return 'console.log() function'
}
}],
})
进行使用 这个注入设置配置完成后,就可以到girl.controller.ts
进行引入使用了。
import {BoyService} from './../boy/boy.service';
引入后记得再constructor
构造函数里,进行注入。
constructor(
@Inject('girl') private girlService:GirlService,
@Inject('GirlArray') private girls:string[],
@Inject('MyFactory') private myFactory:string,
private BoyService:BoyService
){}
然后我们改写下面的test
方法。
@Get('/test')
test():string{
return this.BoyService.findAll()
}
到浏览器中查看最终效果,如果配置一切正常,可以看到已经完成了跨模块的使用。
全局模块的使用
除了这种独立模块的创建和使用,我们还可以创建全局模块。全局模块的功能可以下发到子模块,然后子模块都可以使用。 1.创建全局模块 例如我们要创建一个config
的全局模块,然后让每个模块都可以使用。这里我们先在/src
文件夹下创建一个config
文件夹,然后在里边创建config.module.ts
文件,编写下面的内容。
import {Module,Global} from '@nestjs/common'
@Global()
@Module({
providers:[{
provide:"Config",
useValue:{shopName:"红浪漫"}
}],
exports:[{
provide:"Config",
useValue:{shopName:"红浪漫"}
}]
})
export class ConfigModule{}
2.设置全局引入 有了文件后,我们还要配置app.module.ts
文件,先把全局模块进行引入。
import {ConfigModule} from './config/config.module'
然后在文件中的imports
里写入下面代码就可以了。
@Module({
imports: [GirlModule, BoyModule,ConfigModule
......
})
3.进行使用 上面两步完成后,我们就可以在任何模块中使用全局模块的内容了,比如我们要在Girl模块中进行使用。打开/src/girl/girl.controller.ts
文件,新设置依赖注入。
constructor(
....
@Inject('Config') private shopName:string,
private BoyService:BoyService,
){}
有了这个依赖注入后,就值可以在方法中直接使用了。
@Get('/test')
test():string{
return this.shopName
}
写完方法后,我们再到浏览器中访问localhost:3000/girl/test/
路径,就可以看到这个全局模块中的内容已经可以使用了。 好了,这就是本节课的内容了,下节课我们继续学习模块中的动态模块相关的知识。
17.模块Module-动态模块的编写
接着上节课讲动态模块相关的内容。什么是动态模块那?其实就是在调用模块的时候可以进行传参的模块。里边的知识点就是如何接收参数,如何传递参数。
创建动态模块
我为了省事(懒),这里还是在config
模块里继续编写。NestJS提供了一套固定写法,所以按照规则写就问题不大。打开/src/config/config.module.ts
文件。
export class ConfigModule{
static forRoot (option:string):DynamicModule{
return {
module:ConfigModule,
providers:[{
provide:"Config",
useValue:{shopName:"红浪漫"+option}
}],
exports:[{
provide:"Config",
useValue:{shopName:"红浪漫"+option}
}]
}
}
}
设置动态路由,需要先编写一个静态方法,然后返回的参数需要是DynamicModule
类型就可以实现动态模块了(注意这个DynamicModule需要用import进行引入)。这里我们传入一个参数option
,要求是字符串类型。然后我们设置返回参数。这时候在@Module
装饰器下的值就可以移入到这里进行编写了。 这样一个动态模块的就建立好了。
传值进行使用
当动态模块编写完成后,我们还要进行使用。代码回到app.module.ts
里,在imports
里,给ConfigModule
增加forRoot('洗浴中心')
方法。
@Module({
imports: [GirlModule, BoyModule,ConfigModule.forRoot('洗浴中心'),TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
username:'root', // 连接账号
password:'root123', // 连接密码
database:'nest_demo', // 连接的表名
retryDelay:500, // 重试连接数据库间隔
retryAttempts:10, // 充实次数
synchronize:true, // 是否将实体同步到数据库
autoLoadEntities:true, // 自动加载实体配置,forFeature()注册的每个实体都自己动加载
}) ],
controllers: [],
providers: [],
})
这样就可以给动态模块进行传值了。我们启动服务,然后访问一下http://localhost:3000/girl/test
可以看到结果已经生成了。 好了,这节课内容比较短,但是模块相关的内容还是有些难理解。希望你看完后,包括上节的内容,都能多敲几遍,加深理解和熟练度。
18.数据库操作-TypeORM中的一对一关系
首先非常抱歉,因为个人原因,很久没更新这套视频了。这期间想过放弃,但当看到有很多小伙伴跟着我学习,催更时,我决定把这套视频更新完成。以前的项目由于硬盘损坏,已经没有了。所以我这里从新创建了一个项目,并且从新作了一个数据库。
所以这节课我们会介绍一下这个新的项目,也算是教程回归的一个小调整。然后主要讲讲TypeORM中的一对一关系如何设置。这也是群里问的比较多的一个问题。
介绍一下目前项目环境
我从新在D盘的code目录下面新建了一个Honglangman
的项目,然后我用nest g rs girl —no-spec
生成了一个girl
的模块。又安装了TypeORM相关的组件和配置了数据库相关的信息。 然后在app.module.ts
文件中加入数据库配置。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GirlModule } from './girl/girl.module';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
username:'root', // 连接账号
password:'root123', // 连接密码
database:'honglangman', // 连接的表名
retryDelay:500, // 重试连接数据库间隔
retryAttempts:10, // 允许重连次数
}),
GirlModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
错误问题
就这个机会,回答一下小伙伴提出的问题。这个问题有点意思,我看他的代码也是折腾了一上午,才解决掉。所以在这里分享一下,防止其他小伙伴踩坑。
问题是这样的:当我们在app.module.ts
里配置数据库连接时,需要开启synchronize:true
。但我们第一次 没有数据表时,是没有问题的,但是第二次再开启服务时,就会报Table 'xxx' already exists
的错误。 其实这个错误再Mac和Linux上就不会存在。所以可以把问题锁定在大小写上。这时候我们把配置项中的数据表项改为全小写,就不会报这个错误。 错误的写法:
TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
database:'HongLangMan', // 连接的表名
//.......
}),
正确写法:
TypeOrmModule.forRoot({
type:'mysql', // 数据库类型
host:'localhost', // 数据库的连接地址host
port:3306, // 数据库的端口 3306
database:'honglangman', // 连接的表名
//.......
}),
介绍一下数据库
言归正传,再来看实体是如何设置编写的。数据库中设置了两张表,一张为girl
表,里边时女孩的基本信息,一张是girlDetail
表,这里边是女孩的详细信息。比如女孩的身高、体重、技能.....等等。这里我是使用NestJS
实体来建立的。 你在这里可以把这个看作是一个小练习。如果这些代码和表都通过NestJS设置好,接下来就可以跟着我一起编写代码了。我也会把这代码放到Github上,方便想偷懒的小伙伴,可以直接下载源码学习。
Github源码地址:https://github.com/shenghy/HonglangManNestJS
这样设置表,也是为了讲一个重要的知识点,一对一关系的设置。下面的TypeORM一对一关系。
一对一关系设置
先来说原理,为什么会有一对一关系?有的小伙伴会认为一对一关系,放到一个表中不就可以吗?作为一个前端开始我也有这样的疑问。但一对一关系在实际项目中非常常见,这样的设计可以大大改善查询的效率。比如我们的红浪漫表里有很多女孩,假设5000条数据。进入程序首页,要显示一个简介列表。这时候为了查询性能的有优化就要设计一对一关系,没必要显示在列表中的信息,我们就放在附表里,也就是`girlDetail`表。比如女孩的身高、体重和技能。 说完原理,我们再动手设置一下NestJS TypeORM如何设置一对一关系。打开/src/girl/entities/girlDetail.entity.ts
文件,设置一对一关系。在代码的最后,先使用@oneToOne
修饰符,再使用箭头函数的形式设置对应的类,就是Girl这个类。再使用@JoinColumn()
这个修饰符,最后代码如下。
import {Entity ,OneToOne, Column ,PrimaryGeneratedColumn, JoinColumn} from 'typeorm'
import{ Girl } from './girl.entity'
@Entity()
export class GirlDetail {
//...
@OneToOne(()=>Girl)
@JoinColumn()
girl:Girl
}
设置好后,保存代码,如果没有错误。再到数据库中查看,一对一的数据表关系就设置好了。
自定义列名
现在数据表中的拼接方式,是以小驼峰的方式进行拼接的,如果你不喜欢这种方式,可以自定义拼接方式。例如设置为`girl_id`。就可以修改代码为下面的形式。
@OneToOne(()=>Girl)
@JoinColumn({name:'girl_id'})
girl:Girl
这时候数据表中的列名就变成了girl_id
了。 这里的难点是,这时候我们使用的编程思想都是面向对象的,把一个表看成一个类,然后实例就是对象。所以我们这里在设置OneToOne
的时候,我们了这个对象实例的方式。 好了,这节课的内容就先到这里了,希望小伙伴们花点时间,把这节课需要的代码都编写一下,然后继续跟着技术胖一起学习。
19.数据库操作-TypeORM中一对多关系
这节讲一下TypeORM中一对多的关系。先来了解在什么情况下会产生一对多的关系。比如我们的红浪漫开始营业了,我们要记录一个按摩师服务过多少顾客,每服务一个顾客就产生一条服务记录。方便我们月底给员工发工资永。
建立Order模块
我们使用命令创建一个order模块。
nest g res order --no-spec
建立好后,在order
模块中创建一个order.entity.ts
的实体,然后编写实体代码如下。
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Order {
@PrimaryGeneratedColumn()
orderId : number;
@Column()
orderDate : Date;
@Column()
orderMoney : number;
}
注意,这时候我们并没有建立一堆多的关系。
一对多关系的建立
当我们的order
实体建立好以后,我们再回到girl
模块的实体中,来作一对多的设置。
在girl.entiy.ts
文件的最后编写下面的代码。
@OneToMany(()=>Order,(order)=>order.girl)
@JoinColumn()
order:Order[]; // 一对多关系
编写完成,我们再用import
把Order
实体引入过来。
import {Order} from '../../order/entities/order.entity'
这时候order.girl
会报错,因为我们还需要去Order
实体中,设置多对一的关系ManyToOne
。
打开order.entity.ts
文件,在最后设置多对一
关系.
@ManyToOne(()=>Girl, girl=>girl.order)
girl:Girl; // 多对一关系
写完后,记得还是需要把girl实体
引入的。
import {Girl} from '../../girl/entities/girl.entity'
这样我们就做好了一堆多的关系。
在模块中引入
当我们做好了实体后,还需要到新建立的order.module.ts
里用imports
把实体引入一下。
import { Module } from '@nestjs/common';
import { OrderService } from './order.service';
import { OrderController } from './order.controller';
import {TypeOrmModule} from '@nestjs/typeorm'
import {Order } from './entities/order.entity'
@Module({
imports:[TypeOrmModule.forFeature([Order])],
controllers: [OrderController],
providers: [OrderService]
})
export class OrderModule {}
这一步做完后,就可以启动服务npm run start:dev
来看一下效果了。
http://localhost:3000
这时候我们再到数据库中看一下结果,可以看到Order
表已经建立,并且有了一个userId
的字段。这也标志这我们一对多的关系设置成功了。