完整版本,點(diǎn)擊此處查看 http://blog.poetries.top/2022/05/25/nest-summary
Nest (NestJS) 是一個用于構(gòu)建高效、可擴(kuò)展的 Node.js 服務(wù)器端應(yīng)用程序的開發(fā)框架。它利用 JavaScript 的漸進(jìn)增強(qiáng)的能力,使用并完全支持 TypeScript (仍然允許開發(fā)者使用純 JavaScript 進(jìn)行開發(fā)),并結(jié)合了 OOP (面向?qū)ο缶幊蹋?、FP (函數(shù)式編程)和 FRP (函數(shù)響應(yīng)式編程)。
- 在底層,Nest 構(gòu)建在強(qiáng)大的 HTTP 服務(wù)器框架上,例如 Express (默認(rèn)),并且還可以通過配置從而使用 Fastify !
- Nest 在這些常見的 Node.js 框架 (Express/Fastify) 之上提高了一個抽象級別,但仍然向開發(fā)者直接暴露了底層框架的 API。這使得開發(fā)者可以自由地使用適用于底層平臺的無數(shù)的第三方模塊。
本文基于nest8演示
基礎(chǔ)
創(chuàng)建項(xiàng)目
$ npm i -g @nestjs/cli
nest new project-name 創(chuàng)建一個項(xiàng)目
$ tree
.
├── README.md
├── nest-cli.json
├── package.json
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json
2 directories, 12 files
以下是這些核心文件的簡要概述:
-
app.controller.ts帶有單個路由的基本控制器示例。 -
app.module.ts應(yīng)用程序的根模塊。 -
main.ts應(yīng)用程序入口文件。它使用 NestFactory 用來創(chuàng)建 Nest 應(yīng)用實(shí)例。
main.ts包含一個異步函數(shù),它負(fù)責(zé)引導(dǎo)我們的應(yīng)用程序:
import { NestFactory } from '@nestjs/core';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
await app.listen(3000);
}
bootstrap();
-
NestFactory暴露了一些靜態(tài)方法用于創(chuàng)建應(yīng)用實(shí)例 -
create()方法返回一個實(shí)現(xiàn)INestApplication接口的對象, 并提供一組可用的方法
nest有兩個支持開箱即用的 HTTP 平臺:express和fastify。 您可以選擇最適合您需求的產(chǎn)品
-
platform-expressExpress 是一個眾所周知的 node.js 簡約 Web 框架。 這是一個經(jīng)過實(shí)戰(zhàn)考驗(yàn),適用于生產(chǎn)的庫,擁有大量社區(qū)資源。 默認(rèn)情況下使用@nestjs/platform-express包。 許多用戶都可以使用Express,并且無需采取任何操作即可啟用它。 -
platform-fastifyFastify是一個高性能,低開銷的框架,專注于提供最高的效率和速度。
Nest控制器
Nest中的控制器層負(fù)責(zé)處理傳入的請求, 并返回對客戶端的響應(yīng)。
[圖片上傳失敗...(image-5b262f-1653558123233)]
控制器的目的是接收應(yīng)用的特定請求。路由機(jī)制控制哪個控制器接收哪些請求。通常,每個控制器有多個路由,不同的路由可以執(zhí)行不同的操作
通過NestCLi創(chuàng)建控制器:
nest -h 可以看到nest支持的命令
常用命令:
- 創(chuàng)建控制器:
nest g co user module - 創(chuàng)建服務(wù):
nest g s user module - 創(chuàng)建模塊:
nest g mo user module - 默認(rèn)以src為根路徑生成

nest g controller posts
表示創(chuàng)建posts的控制器,這個時候會在src目錄下面生成一個posts的文件夾,這個里面就是posts的控制器,代碼如下
import { Controller } from '@nestjs/common';
@Controller('posts')
export class PostsController {
}
創(chuàng)建好控制器后,nestjs會自動的在 app.module.ts 中引入PostsController,代碼如下
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PostsController } from './posts/posts.controller'
@Module({
imports: [],
controllers: [AppController, PostsController],
providers: [AppService],
})
export class AppModule {}
nest配置路由請求數(shù)據(jù)
Nestjs提供了其他HTTP請求方法的裝飾器
@Get()@Post()@Put()、@Delete()、@Patch()、@Options()、@Head()和@All()
在Nestjs中獲取Get傳值或者Post提交的數(shù)據(jù)的話我們可以使用Nestjs中的裝飾器來獲取。
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(key?: string) req.params / req.params[key]
@Body(key?: string) req.body / req.body[key]
@Query(key?: string) req.query / req.query[key]
@Headers(name?: string) req.headers / req.headers[name]
示例
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Post('create')
create(@Body() createPostDto: CreatePostDto) {
return this.postsService.create(createPostDto);
}
@Get('list')
findAll(@Query() query) {
return this.postsService.findAll(query);
}
@Get(':id')
findById(@Param('id') id: string) {
return this.postsService.findById(id);
}
@Put(':id')
update(
@Param('id') id: string,
@Body() updatePostDto: UpdatePostDto,
) {
return this.postsService.update(id, updatePostDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.postsService.remove(id);
}
}
注意
-
關(guān)于nest的return: 當(dāng)請求處理程序返回 JavaScript 對象或數(shù)組時,它將自動序列化為 JSON。但是,當(dāng)它返回一個字符串時,Nest 將只發(fā)送一個字符串而不是序列化它
Nest服務(wù)
Nestjs中的服務(wù)可以是
service也可以是provider。他們都可以通過 constructor 注入依賴關(guān)系。服務(wù)本質(zhì)上就是通過@Injectable()裝飾器注解的類。在Nestjs中服務(wù)相當(dāng)于MVC的Model

創(chuàng)建服務(wù)
nest g service posts
創(chuàng)建好服務(wù)后就可以在服務(wù)中定義對應(yīng)的方法
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, Between, Equal, Like, In } from 'typeorm';
import * as dayjs from 'dayjs';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { PostsEntity } from './entities/post.entity';
import { PostsRo } from './interfaces/posts.interface';
@Injectable()
export class PostsService {
constructor(
@InjectRepository(PostsEntity)
private readonly postsRepository: Repository<PostsEntity>,
) {}
async create(post: CreatePostDto) {
const { title } = post;
const doc = await this.postsRepository.findOne({ where: { title } });
console.log('doc', doc);
if (doc) {
throw new HttpException('文章標(biāo)題已存在', HttpStatus.BAD_REQUEST);
}
return {
data: await this.postsRepository.save(post),
message: '創(chuàng)建成功',
};
}
// 分頁查詢列表
async findAll(query = {} as any) {
let { pageSize, pageNum, orderBy, sort, ...params } = query;
orderBy = query.orderBy || 'create_time';
sort = query.sort || 'DESC';
pageSize = Number(query.pageSize || 10);
pageNum = Number(query.pageNum || 1);
console.log('query', query);
const queryParams = {} as any;
Object.keys(params).forEach((key) => {
if (params[key]) {
queryParams[key] = Like(`%${params[key]}%`); // 所有字段支持模糊查詢、%%之間不能有空格
}
});
const qb = await this.postsRepository.createQueryBuilder('post');
// qb.where({ status: In([2, 3]) });
qb.where(queryParams);
// qb.select(['post.title', 'post.content']); // 查詢部分字段返回
qb.orderBy(`post.${orderBy}`, sort);
qb.skip(pageSize * (pageNum - 1));
qb.take(pageSize);
return {
list: await qb.getMany(),
totalNum: await qb.getCount(), // 按條件查詢的數(shù)量
total: await this.postsRepository.count(), // 總的數(shù)量
pageSize,
pageNum,
};
}
// 根據(jù)ID查詢詳情
async findById(id: string): Promise<PostsEntity> {
return await this.postsRepository.findOne({ where: { id } });
}
// 更新
async update(id: string, updatePostDto: UpdatePostDto) {
const existRecord = await this.postsRepository.findOne({ where: { id } });
if (!existRecord) {
throw new HttpException(`id為${id}的文章不存在`, HttpStatus.BAD_REQUEST);
}
// updatePostDto覆蓋existRecord 合并,可以更新單個字段
const updatePost = this.postsRepository.merge(existRecord, {
...updatePostDto,
update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
});
return {
data: await this.postsRepository.save(updatePost),
message: '更新成功',
};
}
// 刪除
async remove(id: string) {
const existPost = await this.postsRepository.findOne({ where: { id } });
if (!existPost) {
throw new HttpException(`文章ID ${id} 不存在`, HttpStatus.BAD_REQUEST);
}
await this.postsRepository.remove(existPost);
return {
data: { id },
message: '刪除成功',
};
}
}
Nest模塊
模塊是具有
@Module()裝飾器的類。@Module()裝飾器提供了元數(shù)據(jù),Nest 用它來組織應(yīng)用程序結(jié)構(gòu)
[圖片上傳失敗...(image-614ea9-1653558123233)]
每個 Nest 應(yīng)用程序至少有一個模塊,即根模塊。根模塊是 Nest 開始安排應(yīng)用程序樹的地方。事實(shí)上,根模塊可能是應(yīng)用程序中唯一的模塊,特別是當(dāng)應(yīng)用程序很小時,但是對于大型程序來說這是沒有意義的。在大多數(shù)情況下,您將擁有多個模塊,每個模塊都有一組緊密相關(guān)的功能。
@module() 裝飾器接受一個描述模塊屬性的對象:
-
providers由 Nest 注入器實(shí)例化的提供者,并且可以至少在整個模塊中共享 -
controllers必須創(chuàng)建的一組控制器 -
imports導(dǎo)入模塊的列表,這些模塊導(dǎo)出了此模塊中所需提供者 -
exports由本模塊提供并應(yīng)在其他模塊中可用的提供者的子集
// 創(chuàng)建模塊 posts
nest g module posts
Nestjs中的共享模塊
每個模塊都是一個共享模塊。一旦創(chuàng)建就能被任意模塊重復(fù)使用。假設(shè)我們將在幾個模塊之間共享 PostsService 實(shí)例。 我們需要把 PostsService 放到 exports 數(shù)組中:
// posts.modules.ts
import { Module } from '@nestjs/common';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
@Module({
controllers: [PostsController],
providers: [PostsService],
exports: [PostsService] // 共享模塊導(dǎo)出
})
export class PostsModule {}
可以使用
nest g res posts一鍵創(chuàng)建以上需要的各個模塊
[圖片上傳失敗...(image-890f8d-1653558123233)]
配置靜態(tài)資源
NestJS中配置靜態(tài)資源目錄完整代碼
npm i @nestjs/platform-express -S
import { NestExpressApplication } from '@nestjs/platform-express';
// main.ts
async function bootstrap() {
// 創(chuàng)建實(shí)例
const app = await NestFactory.create<NestExpressApplication>(AppModule);
//使用方式一
app.useStaticAssets('public') //配置靜態(tài)資源目錄
// 使用方式二:配置前綴目錄 設(shè)置靜態(tài)資源目錄
app.useStaticAssets(join(__dirname, '../public'), {
// 配置虛擬目錄,比如我們想通過 http://localhost:3000/static/1.jpg 來訪問public目錄里面的文件
prefix: '/static/', // 設(shè)置虛擬路徑
});
// 啟動端口
const PORT = process.env.PORT || 9000;
await app.listen(PORT, () =>
Logger.log(`服務(wù)已經(jīng)啟動 http://localhost:${PORT}`),
);
}
bootstrap();
配置模板引擎
npm i ejs --save
配置模板引擎
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import {join} from 'path';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放視圖的文件
app.setViewEngine('ejs'); //模板渲染引擎
await app.listen(9000);
}
bootstrap();
項(xiàng)目根目錄新建views目錄然后新建根目錄 -> views -> default -> index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h3>模板引擎</h3>
<%=message%>
</body>
</html>
渲染頁面
Nestjs中 Render裝飾器可以渲染模板,使用路由匹配渲染引擎
mport { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
@Get()
@Render('default/index') //使用render渲染模板引擎,參數(shù)就是文件路徑:default文件夾下的index.ejs
getUser(): any {
return {message: "hello word"} //只有返回參數(shù)在模板才能獲取,如果不傳遞參數(shù),必須返回一個空對象
}
}
Cookie的使用
cookie和session的使用依賴于當(dāng)前使用的平臺,如:express和fastify
兩種的使用方式不同,這里主要記錄基于express平臺的用法
cookie可以用來存儲用戶信息,存儲購物車等信息,在實(shí)際項(xiàng)目中用的非常多
npm instlal cookie-parser --save
npm i -D @types/cookie-parser --save
引入注冊
// main.ts
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import * as cookieParser from 'cookie-parser'
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
//注冊cookie
app.use(cookieParser('dafgafa')); //加密密碼
await app.listen(3000);
}
bootstrap();
接口中設(shè)置cookie 使用response
請求該接口,響應(yīng)一個cookie
@Get()
index(@Response() res){
//設(shè)置cookie, signed:true加密
//參數(shù):1:key, 2:value, 3:配置
res.cookie('username', 'poetry', {maxAge: 1000 * 60 * 10, httpOnly: true, signed:true})
//注意:
//使用res后,返回數(shù)據(jù)必須使用res
//如果是用了render模板渲染,還是使用return
res.send({xxx})
}
cookie相關(guān)配置參數(shù)
-
domainString 指定域名下有效 -
expiresDate 過期時間(秒),設(shè)置在某個時間點(diǎn)后會在該cookoe后失效 -
httpOnlyBoolean 默認(rèn)為false如果為true表示不允許客戶端(通過js來獲取cookie) -
maxAgeString 最大失效時間(毫秒),設(shè)置在多少時間后失效 -
pathString 表示cookie影響到的路徑,如:path=/如果路徑不能匹配的時候,瀏覽器則不發(fā)送這個cookie -
secureBoolean 當(dāng)secure值為true時,cookie在 HTTP 中是無效,在HTTPS中才有效 -
signedBoolean 表示是否簽名cookie,如果設(shè)置為true的時候表示對這個cookie簽名了,這樣就需要用res.signedCookies()獲取值cookie不是使用res.cookies()了
獲取cookie
@Get()
index(@Request() req){
console.log(req.cookies.username)
//加密的cookie獲取方式
console.log(req.signedCookies.username)
return req.cookies.username
}
Cookie加密
// 配置中間件的時候需要傳參
app.use(cookieParser('123456'));
// 設(shè)置cookie的時候配置signed屬性
res.cookie('userinfo','hahaha',{domain:'.ccc.com',maxAge:900000,httpOnly:true,signed:true});
// signedCookies調(diào)用設(shè)置的cookie
console.log(req.signedCookies);
....
完整版本,點(diǎn)擊此處查看 http://blog.poetries.top/2022/05/25/nest-summary