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阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容