nestJS學習總結篇

完整版本,點擊此處查看 http://blog.poetries.top/2022/05/25/nest-summary

Nest (NestJS) 是一個用于構建高效、可擴展的 Node.js 服務器端應用程序的開發框架。它利用 JavaScript 的漸進增強的能力,使用并完全支持 TypeScript (仍然允許開發者使用純 JavaScript 進行開發),并結合了 OOP (面向對象編程)、FP (函數式編程)和 FRP (函數響應式編程)。

  • 在底層,Nest 構建在強大的 HTTP 服務器框架上,例如 Express (默認),并且還可以通過配置從而使用 Fastify !
  • Nest 在這些常見的 Node.js 框架 (Express/Fastify) 之上提高了一個抽象級別,但仍然向開發者直接暴露了底層框架的 API。這使得開發者可以自由地使用適用于底層平臺的無數的第三方模塊。

本文基于nest8演示

基礎

創建項目

$ npm i -g @nestjs/cli

nest new project-name 創建一個項目

$ 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 應用程序的根模塊。
  • main.ts 應用程序入口文件。它使用 NestFactory 用來創建 Nest 應用實例。

main.ts 包含一個異步函數,它負責引導我們的應用程序:

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 暴露了一些靜態方法用于創建應用實例
  • create() 方法返回一個實現 INestApplication 接口的對象, 并提供一組可用的方法

nest有兩個支持開箱即用的 HTTP 平臺:expressfastify。 您可以選擇最適合您需求的產品

  • platform-express Express 是一個眾所周知的 node.js 簡約 Web 框架。 這是一個經過實戰考驗,適用于生產的庫,擁有大量社區資源。 默認情況下使用 @nestjs/platform-express 包。 許多用戶都可以使用 Express ,并且無需采取任何操作即可啟用它。
  • platform-fastify Fastify 是一個高性能,低開銷的框架,專注于提供最高的效率和速度。

Nest控制器

Nest中的控制器層負責處理傳入的請求, 并返回對客戶端的響應。

[圖片上傳失敗...(image-5b262f-1653558123233)]

控制器的目的是接收應用的特定請求。路由機制控制哪個控制器接收哪些請求。通常,每個控制器有多個路由,不同的路由可以執行不同的操作

通過NestCLi創建控制器:

nest -h 可以看到nest支持的命令

常用命令:

  • 創建控制器:nest g co user module
  • 創建服務:nest g s user module
  • 創建模塊:nest g mo user module
  • 默認以src為根路徑生成
image.png
nest g controller posts

表示創建posts的控制器,這個時候會在src目錄下面生成一個posts的文件夾,這個里面就是posts的控制器,代碼如下

import { Controller } from '@nestjs/common';

@Controller('posts')
export class PostsController {
}

創建好控制器后,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配置路由請求數據

Nestjs提供了其他HTTP請求方法的裝飾器 @Get() @Post() @Put()@Delete()@Patch()@Options()@Head()@All()

在Nestjs中獲取Get傳值或者Post提交的數據的話我們可以使用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);
  }
}

注意

  • 關于nest的return: 當請求處理程序返回 JavaScript 對象或數組時,它將自動序列化為 JSON。但是,當它返回一個字符串時,Nest 將只發送一個字符串而不是序列化它

Nest服務

Nestjs中的服務可以是service 也可以是provider。他們都可以通過 constructor 注入依賴關系。服務本質上就是通過@Injectable() 裝飾器注解的類。在Nestjs中服務相當于MVCModel

image.png

創建服務

nest g service posts

創建好服務后就可以在服務中定義對應的方法

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('文章標題已存在', HttpStatus.BAD_REQUEST);
    }
    return {
      data: await this.postsRepository.save(post),
      message: '創建成功',
    };
  }

  // 分頁查詢列表
  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(), // 按條件查詢的數量
      total: await this.postsRepository.count(), // 總的數量
      pageSize,
      pageNum,
    };
  }

  // 根據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() 裝飾器提供了元數據,Nest 用它來組織應用程序結構

[圖片上傳失敗...(image-614ea9-1653558123233)]

每個 Nest 應用程序至少有一個模塊,即根模塊。根模塊是 Nest 開始安排應用程序樹的地方。事實上,根模塊可能是應用程序中唯一的模塊,特別是當應用程序很小時,但是對于大型程序來說這是沒有意義的。在大多數情況下,您將擁有多個模塊,每個模塊都有一組緊密相關的功能。

@module() 裝飾器接受一個描述模塊屬性的對象:

  • providers 由 Nest 注入器實例化的提供者,并且可以至少在整個模塊中共享
  • controllers 必須創建的一組控制器
  • imports 導入模塊的列表,這些模塊導出了此模塊中所需提供者
  • exports 由本模塊提供并應在其他模塊中可用的提供者的子集
// 創建模塊 posts
nest g module posts

Nestjs中的共享模塊

每個模塊都是一個共享模塊。一旦創建就能被任意模塊重復使用。假設我們將在幾個模塊之間共享 PostsService 實例。 我們需要把 PostsService 放到 exports 數組中:

// 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] // 共享模塊導出
})
export class PostsModule {}

可以使用 nest g res posts 一鍵創建以上需要的各個模塊

[圖片上傳失敗...(image-890f8d-1653558123233)]

配置靜態資源

NestJS中配置靜態資源目錄完整代碼

npm i @nestjs/platform-express -S
import { NestExpressApplication } from '@nestjs/platform-express';
// main.ts
async function bootstrap() {
  // 創建實例
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  
   //使用方式一
  app.useStaticAssets('public')  //配置靜態資源目錄
  
  // 使用方式二:配置前綴目錄 設置靜態資源目錄
  app.useStaticAssets(join(__dirname, '../public'), {
    // 配置虛擬目錄,比如我們想通過 http://localhost:3000/static/1.jpg 來訪問public目錄里面的文件
    prefix: '/static/', // 設置虛擬路徑
  });
  // 啟動端口
  const PORT = process.env.PORT || 9000;
  await app.listen(PORT, () =>
    Logger.log(`服務已經啟動 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();

項目根目錄新建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渲染模板引擎,參數就是文件路徑:default文件夾下的index.ejs
  getUser(): any {
    return {message: "hello word"}   //只有返回參數在模板才能獲取,如果不傳遞參數,必須返回一個空對象
  }
}

Cookie的使用

cookie和session的使用依賴于當前使用的平臺,如:express和fastify
兩種的使用方式不同,這里主要記錄基于express平臺的用法

cookie可以用來存儲用戶信息,存儲購物車等信息,在實際項目中用的非常多

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();

接口中設置cookie 使用response

請求該接口,響應一個cookie

@Get()
index(@Response() res){
    //設置cookie, signed:true加密
    //參數:1:key, 2:value, 3:配置
    res.cookie('username', 'poetry', {maxAge: 1000 * 60 * 10, httpOnly: true, signed:true})
    
    //注意:
    //使用res后,返回數據必須使用res
    //如果是用了render模板渲染,還是使用return
    res.send({xxx})
}

cookie相關配置參數

  • domain String 指定域名下有效
  • expires Date 過期時間(秒),設置在某個時間點后會在該cookoe后失效
  • httpOnly Boolean 默認為false 如果為true表示不允許客戶端(通過js來獲取cookie)
  • maxAge String 最大失效時間(毫秒),設置在多少時間后失效
  • path String 表示cookie影響到的路徑,如:path=/如果路徑不能匹配的時候,瀏覽器則不發送這個cookie
  • secure Boolean 當 secure 值為 true 時,cookie 在 HTTP 中是無效,在 HTTPS 中才有效
  • signed Boolean 表示是否簽名cookie,如果設置為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'));

// 設置cookie的時候配置signed屬性
res.cookie('userinfo','hahaha',{domain:'.ccc.com',maxAge:900000,httpOnly:true,signed:true});

// signedCookies調用設置的cookie
console.log(req.signedCookies);  

....

完整版本,點擊此處查看 http://blog.poetries.top/2022/05/25/nest-summary

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容