NestJS从入门到跑路

仅做参考: 有些语法已经废弃,

什么是NestJS

Nest 是一个渐进的 Node.js 框架,可以在 TypeScript 和 JavaScript (ES6、ES7、ES8)之上构 建高效、可伸缩的企业级服务器端应用程序。

Nest 基于 TypeScript 编写并且结合了 OOP(面向对象编程),FP(函数式编程)和 FRP (函数式响应编程)的相关理念。在设计上的很多灵感来自于 Angular,Angular 的很多模 式又来自于 Java 中的 Spring 框架,依赖注入、面向切面编程等,所以我们也可以认为: Nest 是 Node.js 版的 Spring 框架。

Nest 框架底层 HTTP 平台默认是基于 Express 实现的,所以无需担心第三方库的缺失。 Nest 旨在成为一个与平台无关的框架。 通过平台,可以创建可重用的逻辑部件,开发人员可以利用这些部件来跨越多种不同类型的应用程序。 从技术上讲,Nest 可以在创建适配器 后使用任何 Node HTTP 框架。 有两个支持开箱即用的 HTTP 平台:express 和 fastify。 您 可以选择最适合您需求的产品。

NestJs 的核心思想:就是提供了一个层与层直接的耦合度极小,抽象化极高的一个架构 体系。

官网:https://nestjs.com/

中文网站:https://docs.nestjs.cn/

GitHub: https://github.com/nestjs/nest

image

图一只是说明规范的模块方式,实际上,可以只有根模块,也可以划分多个模块,互相依赖,只要不是循环引入就行。

Nestjs 的特性

  • 依赖注入容器
  • 模块化封装
  • 可测试性
  • 内置支持 TypeScript
  • 可基于 Express 或者 fastify

脚手架nest-cli

安装

npm i -g @nestjs/cli 或者 cnpm i -g @nestjs/cli 或者 yarn global add @nestjs/cli

创建

nest new nestdemo

相关指令

  • nest new 名称 创建项目
  • nest -h/--help 帮助
  • nest g co 名称 创建控制器
  • nest g s 名称 创建服务
  • nest g mi 名称 创建中间件
  • nest g pi 名称 创建管道
  • nest g mo 名称 创建模块
  • nest g gu 名称 创建守卫

创建类型指令都可以指定文件路径,而且路径全部是再src目录下面,例如:
nest g co /aaa/bbb/user 则在src下面就会存在一个三级目录,user的目录下
有一个以user命名大写的控制器 UserController.ts文件

注意:凡是以脚手架创建的模块,控制器等等都会自动添加到对应配置位置,不需要手动配置

控制器

Nest 中的控制器层负责处理传入的请求, 并返回对客户端的响应。

import { Controller, Get } from '@nestjs/common';
@Controller('article')
export class ArticleController { 
    @Get() 
    index(): string { 
        return '这是 article 里面的 index'; 
    } 
    @Get('add') 
    add(): string { 
        return '这是 article 里面的 index'; 
    } 
}

关于 nest 的 return: 当请求处理程序返回 JavaScript 对象或数组时,它将自动序列化为 JSON。但是,当它返回一个字符串时,Nest 将只发送一个字符串而不是序列化它。这使响应处理变得简单:只需要返回值,Nest 负责其余部分。

Get Post通过方法参数装饰器获取传值

基本栗子

nestjs 内置装饰器的时候必须得在@nestjs/common 模块下面引入对应的装饰器

import { Controller, Get, Post } from '@nestjs/common'; 
@Controller('cats') 
export class CatsController { 
    @Post() 
    create(): string { 
        return 'This action adds a new cat'; 
    } 
    @Get() 
    findAll(): string { 
        return 'This action returns all cats'; 
    } 
}

Nestjs 也提供了其他 HTTP 请求方法的装饰器 @Put() 、@Delete()、@Patch()、 @Options()、 @Head()和 @All()

Nest中获取请求参数

在 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]
import { Controller, Get, Post,Query,Body } from '@nestjs/common'; 
@Controller('news') 
export class NewsController { 
    @Get() 
    getAbout(@Query() query): string { 
        console.log(query); 
        //这里获取的就是所有的 Get 传值 
        return '这是 about'
    }

    //针对参数是 localhost:3000/news/list?id=zq&age=12
    @Get('list') 
    getNews(@Query('id') id):string { 
        console.log(id); 
        //这里获取的就是 Get 传值里面的 Id 的值 
        //如果@Query()则是整个id=zq&age=12的对象
        return '这是新闻' 
    }
    @Post('doAdd') 
    async addNews(@Body() newsData){ 
        console.log(newsData); 
        return '增加新闻’'
    } 
}

动态路由

// 针对的参数是 /name/id这种类型,例如/name/1

@Get(':id') 
findOne(@Param() params): string { 
    console.log(params.id); 
    return `This action returns a #${params.id} cat`; 
}

补充: @Param() 装饰器访问以这种方式声明的路由参数,该装饰器应添 加到函数签名中。@Param可以用在get或者post,但是都是针对 localhost:3000/news/list?id=zq&age=12这种才可以获取,而针对body内部都是获取不到的,可以使用@Body

综合案例

@Controller('news')
export class NewsController {
    //依赖注入
    constructor(private readonly newsService:NewsService){}
    @Get('pip')
    @UsePipes(new NewsPipe(useSchema))
    indexPip(@Query() info){
        // console.log(info);
        return info;
    }
    
    @Get()
    @Render('default/news')
    index(){
      return  {
          newsList:this.newsService.findAll()
      }
    }

    /**
     * 路由顺序:如果此时访问http://localhost:3000/news/add
     * 则会正确执行,如果把add移动到:id下面,则只会执行:id的
     */
    @Get('add')
    addData(@Query('id') id){
        return id+'------';
    }

    //同理,这个模糊匹配如果移动到:id下面,访问http://localhost:3000/news/aaa
    //也会只匹配:id的路由
    @Get('a*a')
    indexA(){
        return '模糊匹配';
    }

    //动态路由  /add/1
    @Get(':id')
    indexB(@Param('id') id){
        return id;
    }
}

Swagger集成

中文文档地址:https://docs.nestjs.cn/6/recipes?id=openapi-swagger

  • 安装

npm install --save @nestjs/swagger swagger-ui-express

如果你正在使用fastify,你必须安装 fastify-swagger 而不是 swagger-ui-express

npm install --save @nestjs/swagger fastify-swagger

  • 引导
    • main.ts
    • 引入:import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
    • 编码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    const options = new DocumentBuilder()
        .setTitle('Cats example')
        .setDescription('The cats API description')
        .setVersion('1.0')
        //下面两者结合组成请求基本路径
        .setHost('http://www.baidu.com')
        .setBasePath('/api')
        // .addTag('cats') 分类
        .build();
const document = SwaggerModule.createDocument(app, options);
//指定文档路径
SwaggerModule.setup('api-docs', app, document);
await app.listen(3000);
}
bootstrap();

打开http://localhost:3000/api-docs/#/,如下图二

image

swagger基本使用

创建Dto

import { ApiModelProperty } from '@nestjs/swagger';
export class CreatePostDto{
  @ApiModelProperty({description:"应用名称",example:'示例值'})
  title:string
  @ApiModelProperty({description:"应用内容"})
  content:string
}

@ApiModelProperty() 装饰器接受选项对象

export const ApiModelProperty: (metadata?: {
  description?: string;
  required?: boolean;  //代表是否必须存在该参数
  type?: any;
  isArray?: boolean;
  collectionFormat?: string;
  default?: any;
  enum?: SwaggerEnumType;
  format?: string;
  multipleOf?: number;
  maximum?: number;
  exclusiveMaximum?: number;
  minimum?: number;
  exclusiveMinimum?: number;
  maxLength?: number;
  minLength?: number;
  pattern?: string;
  maxItems?: number;
  minItems?: number;
  uniqueItems?: boolean;
  maxProperties?: number;
  minProperties?: number;
  readOnly?: boolean;
  xml?: any;
  example?: any;
}) => PropertyDecorator;

完整例子

只是入门,更多例子是使用规则查看文档

import { Controller, Get, Post, Body, Query, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { ApiUseTags, ApiOperation, ApiModelProperty } from '@nestjs/swagger';

class CreatePostDto{
  //默认required都是true,Model上面会有个红色星号,代表必须填写,
  //但是实际上swagger本身不会限制,只是告知作用,swagger本身请求正常
  @ApiModelProperty({description:"应用名称",example:'示例值',maxLength:1,required:false})
  title:string
  @ApiModelProperty({description:"应用内容"})
  content:string
}

@Controller('app')
@ApiUseTags('默认标签')//其实是大分类
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('hello')
  @ApiOperation({title:"显示hello"}) //api的title描述/注释
  getHello(@Query() query,@Param() params): any[] {
    return this.appService.getHello();
  }

  @Post('create')
  @ApiOperation({title:"创建应用"})
  createApp(@Body() body:CreatePostDto): CreatePostDto{
    return body;
  }

  @Get(':id')
  @ApiOperation({title:'应用详情'})
  detail(@Param('id') id:number){
      return{
        id
      }
  }
}

总结:swagger本身注解只是告知作用,不同于graphql,例如required本身是没什么作用只是告知使用者需要填写,但是实际上需要与否还是程序控制;同理,不论填写不填写swagger都会进行请求,最终结果以逻辑控制为准。

参数验证

安装

npm i class-validator class-transformer --save

启用全局管道
main.ts中

app.useGlobalPipes(new ValidationPipe())

导包
在需要使用参数校验的文件内导入

    import { IsNotEmpty } from 'class-validator'

class CreatePostDto{
  @ApiModelProperty({description:"应用名称",example:'示例值'})
  //**此处就是**
  @IsNotEmpty({message:'我是没填写title属性的时候,返回的给前端的错误信息'})
  title:string
  @ApiModelProperty({description:"应用内容"})
  content:string
}

说明: Nest 自带两个开箱即用的管道,即 ==ValidationPipe== 和 ==ParseIntPipe==
补充:ParseIntPipe简单使用

可把id自动转换成Int类型

@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
  return await this.catsService.findOne(id);
}

总结: 内置管道都支持全局,方法,参数三种级别的使用
文档连接

静态资源

官方文档:https://docs.nestjs.com/techniques/mvc

app.useStaticAssets('public');

  • 栗子
async function bootstrap() {
  //指定平台
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  //配置静态资源
  //app.useStaticAssets(join(__dirname,'..','public'))
  
  //上面是直接访问http://localhost:3000/a.png
  //下面可以设置虚拟目录http://localhost:3000/public/a.png
  // app.useStaticAssets(join(__dirname,'..','public'),{
  //   prefix:'/public/'
  // })

  //如下方式也可以,因为默认nest会去寻找根目录下面的参数一文件夹
  app.useStaticAssets('public',{
    prefix:'/public/'  
  })
  await app.listen(3000);
}

注意:NestFactory.create<NestExpressApplication>(AppModule);指定了范型其实就是Nest的平台,默认使用的是express的平台,因为静态资源涉及平台的选择所以必须指定了。

模板引擎

官方文档:https://docs.nestjs.com/techniques/mvc

  • 安装

cnpm i ejs --save

配置

app.setBaseViewsDir(join(__dirname, '..', 'views')) // 放视图的文件
app.setViewEngine('ejs');

完整代码

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
//静态资源中间件依赖于具体平台,所以可以先引入express
import { NestExpressApplication } from '@nestjs/platform-express';
// import { join } from 'path';

//此时下面如果使用path则就是path.join
// import * as  path from 'path';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.useStaticAssets('public',{
    prefix:'/public/'
  })

  //配置模板引擎,需要先安装模板引擎
  // app.setBaseViewsDir(join(__dirname,'..','views'))
  app.setBaseViewsDir('views');
  app.setViewEngine('ejs');
  await app.listen(3000);
}
bootstrap();

==注意此处引入path的方式==

渲染页面

@Controller('user')
export class UserController {
    @Get()
    @Render('default/user')
    index(){
        //注意一般有render的路由则return值都是给模板引擎使用的
        //所以nest会判断,一般都是对象,返回字符串会报错
        // return '用户中心';
        //此处是字符串key还是直接命名都可以被ejs搜索到
        // return {"name":"zs",age:12}
    }
}

说明:default指的是views下面的default文件夹内部的user模板
重定向

import { Controller, Get, Post, Body,Response, Render} from '@nestjs/common';
 @Controller('user') 
 export class UserController { 
     @Get() 
     @Render('default/user') 
     index(){ 
         return {"name":"张三"}; 
    }
    @Post('doAdd') 
    doAdd(@Body() body,@Response() res){
         console.log(body); 
         res.redirect('/user'); //路由跳转 
    } 
}

提供者

几乎所有的东西都可以被认为是提供者 - service, repository, factory, helper 等等。他们都可以通过 constructor注入依赖关系,也就是说,他们可以创建各种关系。但事实上,提供者不过是一个用@Injectable() 装饰器注解的类。

export interface Cat {
  name: string;
  age: number;
  breed: string;
}

import { Controller, Get, Post, Body } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
    //依赖注入
  constructor(private readonly catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }
}

cookie

安装

cnpm instlal cookie-parser --save

在 main.ts 中引入 cookie-parser

import * as cookieParser from 'cookie-parser'

在 main.ts 配置中间件

    app.use(cookieParser());

设置 cookie

    res.cookie("name",'zhangsan',{maxAge: 900000, httpOnly: true});

获取 Cookies

@Get('getCookies') 
getCookies(@Request() req){ 
    return req.cookies.name; 
}

cookie参数说明
属性 说明
domain 域名
expires 过 期 时 间 ( 秒 ) , 在 设 置 的 某 个 时 间 点 后 该 Cookie 就 会 失 效 , 如 expires=Wednesday, 09-Nov-99 23:12:40 GMT
maxAge 最大失效时间(毫秒),设置在多少后失效
secure 当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效
path 表示 cookie 影响到的路,如 path=/。如果路径不能匹配时,浏览器则不发送这 个 Cookie
httpOnly 是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了“httpOnly”属性,则通 过程序(JS 脚本、applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生
signed 表 示 是 否 签 名 cookie, 设 为 true 会 对 这 个 cookie 签 名 , 这 样 就 需 要 用 res.signedCookies 而不是 res.cookies 访问它。被篡改的签名 cookie 会被服务器拒绝,并且 cookie 值会重置为它的原始值,说白了==加密==
相关代码

https://www.jianshu.com/p/eec0586409da

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343