Nestjs学习笔记

全局安装Nestjs cli

cnpm i -g @nestjs/cli

创建Nestjs项目

nest new nestjs-demo

Nsetjs生命周期

在这里插入图片描述

初始化项目

目录文件说明

+-- 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                 // 项目的入口文件,里边包括项目的主模块和监听端口号

三种项目启动脚本说明

"start": "nest start",                              // 最常用的开始模式
"start:dev": "nest start --watch",                  // 开发模式的启动 有监视功能
"start:debug": "nest start --debug --watch",        // 调试Bug时的启动 调试程序时使用

初始化目录结构

删除src下【app.controller.spec.ts、app.controller.ts、app.service.ts】文件,改写【app.module.ts】文件

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

@Module({
  imports: [],
  controllers: [],
  providers: [],
})
export class AppModule {}

nestjs 常用命令

nest --help 可以查看nestjs所有的命令

生成controller.ts

nest g co user --no-spec

–no-spec:不生产测试文件

生成 module.ts

nest g mo user --no-spec

–no-spec:不生产测试文件

生成service.ts

nest g s user --no-spec

–no-spec:不生产测试文件

以上步骤一个一个生成的太慢了我们可以直接使用一个命令生成

nest g resource user

第一次使用这个命令的时候,除了生成文件之外还会自动使用 npm 帮我们更新资源,安装一些额外的插件,后续再次使用就不会更新了。

增加全局前缀

在main.ts中增加【app.setGlobalPrefix】,这时候的访问地址就变成了http://localhost:3000/api/user

...
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  await app.listen(3000);
}
bootstrap();

配置环境变量

.env文件

安装@nestjs/config
pnpm i @nestjs/config

在根目录创建.env文件

DB=mysql
DB_HOST=localhost
DB_USER=root

在app.module.ts中引入

import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    UserModule,
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

isGlobal: true为全局配置

此时,即可在各个文件中引入使用

...
import { ConfigService } from '@nestjs/config';

@Controller('user')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private readonly configService: ConfigService,
  ) {}

  @Get()
  findAll() {
    const db = this.configService.get('DB');//读取配置文件信息
    console.log(db);
   ...
  }
}

config.json配置

  1. 安装config pnpm i config

  2. 在根目录创建config文件夹

  3. 分别创建default.json、development.json、production.json文件

//default.json
{
  "database": {
    "host": "localhost",
    "port": 3306
  }
}

//development.json
{
  "database": {
    "username": "root",
    "password": "123456"
  }
}
  //在使用它的地方引入
  import * as config from 'config';
  import { Controller, Get, Post } from '@nestjs/common';
  import { UserService } from './user.service';
  import * as config from 'config';
  
  @Controller('user')
  export class UserController {
    constructor(
      private readonly userService: UserService
    ) {}
  
    @Get()
    findAll() {
      console.log(config.get('database'));//读取相应的配置文件信息
      return this.userService.findAll(db);
    }
  }

公共模块

新建config文件,包含【config.module.ts】【config.service.ts】文件。

//config.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ConfigService {
  user = 'user';
  userVal() {
    return 'useVal';
  }
}
//config.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';

@Global()//导出模块
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}

然后在【app.module.ts】中引入【config.module.ts】

typeORM

pnpm i --save @nestjs/typeorm typeorm mysql2

使用@nestjs/config连接数据库

//app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'mysql',
        host: config.get('DB_HOST'),
        port: config.get('DB_PORT'),
        username: config.get('DB_USERNAME'),
        password: config.get('DB_PASSWORD'),
        database: config.get('DB_DATABASE'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
        logging: ['error'],
      }),
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}
//.env
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=123456
DB_DATABASE=testdb

使用config.json连接数据库

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as config from 'config';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: config.get('database').host,
      port: config.get('database').port,
      username: config.get('database').username,
      password: config.get('database').password,
      database: config.get('database').database,
      entities: [],
      synchronize: true,
      logging: ['error'],
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

config配置

//default.json
{
  "database": {
    "host": "localhost",
    "port": 3306
  }
}

//development.json
{
  "database": {
    "username": "root",
    "password": "123456"
  }
}

第三方校验模块

pnpm i --save class-validator class-transformer

class-validator

  • 验证数据: class-validator 用于验证类实例的属性是否符合指定的规则。你可以在类的属性上使用装饰器,例如 @IsNotEmpty()@IsString()@IsNumber()等,以声明验证规则。在运行时,这些装饰器会根据规则检查属性的值,并返回验证结果。

  • NestJS中的使用: 在NestJS中,你可以将 class-validator 与 Pipes 一起使用,以在处理请求数据之前验证输入。例如,在处理HTTP请求时,你可以使用 ValidationPipe,它会自动验证请求中的数据,并在发现错误时返回相应的响应。

class-transformer

  • 数据转换: class-transformer 用于将一个数据对象转换为另一个形式。这对于从外部源(例如HTTP请求)接收数据并将其转换为内部数据对象的格式非常有用。你可以使用 @Transform 装饰器,将一个属性的值通过自定义的转换函数映射到另一个属性。

  • NestJS中的使用: 在NestJS中,你可以使用 class-transformerclass-validator 一起工作,以便在验证之前或之后对数据进行转换。这在处理输入数据时特别有用,因为它可以确保你的内部数据格式与外部数据格式分离,并且可以通过转换函数进行适当的处理。

//main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
//whitelist: true -> 去除dto中,不存在的字段
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(3000);
}
bootstrap();

日志

使用nestjs提供的默认日志

//main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: false,//关闭日志
    //logger: ['error', 'warn', 'log'],//设置日志等级
  });
  app.setGlobalPrefix('api');
  await app.listen(3000);
}
bootstrap();
//main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Logger } from '@nestjs/common';//引入Logger 

async function bootstrap() {
 // const logger = new Logger();//另一种使用形式
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  await app.listen(3000);
  Logger.warn('Server is running on port 3000', 'App');//使用Logger
}
bootstrap();

第三方日志模块

winston(勤快的)、pino(懒惰的)

pino

分别安装pino-pretty(格式美化) pino-roll(自动存储)
pnpm i pino-pretty
pnpm i pino-roll
//logs.module.ts
import { Module } from '@nestjs/common';
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    ...
    LoggerModule.forRoot({
      pinoHttp: {
        transport: {
          targets: [
            {
              level: 'info',
              target: 'pino-pretty',
              options: {
                colorize: true,
              },
            },
            {
              level: 'info',
              target: 'pino-roll',
              options: {
                file: './logs/log.text',
                frequency: 'daily',
                mkdir: true,
                maxsize: 10485760, // 10MB
                maxFiles: 10,
              },
            },
          ],
        },
      },
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

公共模块

新建config文件,包含【config.module.ts】【config.service.ts】文件。

//config.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ConfigService {
  user = 'user';
  userVal() {
    return 'useVal';
  }
}
//config.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigService } from './config.service';

@Global()//导出模块
@Module({
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}

然后在【app.module.ts】中引入【config.module.ts】

Controller控制器

  1. 装饰器的执行顺序:方法的装饰器如果有多个,则是从下往上执行

  2. 如果一个UseGuards中传递多个,则从前往后执行,如果前面的Guards没有通过,则不会执行后面的UseGuards

  @Get()
  @UseGuards(AdminGuard) //后执行
  @UseGuards(AuthGuard('jwt')) //先执行
  findAll(@Req() req: any) {
    return this.classifyService.findAll();
  }

按NestJS规定是不应该在controller里写任何的业务逻辑,而是把业务逻辑放在girl.service.ts这个文件中。

Controller(控制器)引入service(逻辑)

service的文件属于逻辑层,按照约束,需要我们把业务逻辑相关的东西都写到这个文件里。当service文件创建好以后,我们需要作的第一件事,就是把service引入到controller控制器里。

import { Controller, Get } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {

  constructor(private readonly userService: UserService) {}
  //this.userService= new UserService();

  @Get()
  getUser(): any {
    return this.userService.getUser();
  }

  @Post('/add')
  addGirl():any{
    return this.girlService.addGirl();
  }
}

在service里编写业务逻辑

在控制器里引入service后,就可以在service里编写业务逻辑了。

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

@Injectable()
export class UserService {
  getUser(): any {
    return {
      code: 200,
      data: '张三',
      msg: '成功',
    };
  }
}

踩坑:nest模块相互引用,如果报错,需检查各模块关联,【app.module】也有引入

nestjs 装饰器

Controller Request (获取前端传过来的参数)

@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]
@HttpCode

获取get请求参数

import { Controller, Get, Post, Query, Request } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  getUser(@Request() req): any {
    console.log(req.query);
    return this.userService.getUser();
  }

  @Get()
  getUser_a(@Query() query): any {
    console.log(query);
    return this.userService.getUser();
  }
}

获取post请求参数

import { Body, Controller, Get, Post, Query, Request } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('/add')
  addUser(@Request() req): any {
    console.log(req.body);
    return this.userService.addUser();
  }
  @Post('/add_a')
  addUser_a(@Body() body): any {
    console.log(body);
    return this.userService.addUser();
  }
}

也可以直接读取key

import { Body, Controller, Get, Post, Query, Request } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post('/add_b')
  addUser_b(@Body('name') body): any {
    console.log(body);
    return this.userService.addUser();
  }
}

读取header 信息

//......

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get('/findId')
  @HttpCode(500) //使用 HttpCode 装饰器 控制接口返回的状态码
  findId(@Headers() header): any {
    console.log(header);
  }
}

动态路由

动态携带参数: http://localhost:3000/api/user/1

......

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get(':id')
  getUser(@Request() req): any {
    console.log(req.params);
    return this.userService.getUser();
  }

  @Get(':id')
  getUser_a(@Param() param): any {
    console.log(param);
    return this.userService.getUser();
  }
}

中间件

NestJS的局部中间件也需要使用命令行来生成,生成的命令如下。

nest g mi counter

局部中间件

中间件可以分为局部中间件和全局中间件,我们先讲局部中间件,也就是应用中的某些路径会进入中间件。

//counter.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class CounterMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction): void {
    console.log('进入到中间件');
    next();
  }
}

中间件写好以后,还要在module文件里写一些代码,让它生效。例如在user中使用中间件,则需要在【user.module.ts】中设置如下代码。

//user.module.ts
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { CounterMiddleware } from '../counter/counter.middleware';

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(CounterMiddleware).forRoutes('user');//需要指定中间件路径
  }
}

全局中间件

在【main.ts】创建函数,并通过app.use()引入即可

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Request, Response, NextFunction } from 'express';

function MiddleWareAll(req: Request, res: Response, next: NextFunction) {
  console.log('我是全局中间件.....');
  next();
}

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(MiddleWareAll);
  await app.listen(3000);
}
bootstrap();

跨域

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors(); //开启跨域
//或者
const app = await NestFactory.create(AppModule,{cors:true});
  await app.listen(3000);
}
bootstrap();

拦截器

通过简单的配置,可以在进入controller前、后进行处理逻辑

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('在进入controller之前');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`在进入controller之后`)),
      );
  }
}

然后通过UseInterceptors绑定

@UseInterceptors(LoggingInterceptor)
export class CatsController {}

响应拦截器

在common下新建【response.ts】拦截器,代码如下:

import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

interface Data<T> {
  data: T;
}

@Injectable()
export class Res<T> implements NestInterceptor {
  intercept(context, next: CallHandler): Observable<Data<T>> {
    return next.handle().pipe(
      map((data) => {
        return {
          data,
          code: 200,
          msg: '成功',
        };
      }),
    );
  }
}

在【main.ts】中注入

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'
import { Res } from './common/response';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new Res());
  await app.listen(3000);
}
bootstrap();

异常拦截器

在common下新建【filter.ts】拦截器,代码如下:

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const request = ctx.getRequest<Request>();
    const response = ctx.getResponse<Response>();
    const status = exception.getStatus();
    response.status(status).json({
      success: false,
      time: new Date(),
      data: exception.message,
      status,
      path: request.url,
    });
  }
}

在【main.ts】中注入

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'
import { HttpFilter } from './common/filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new Res());
  await app.listen(3000);
}
bootstrap();

登录JWT

https://jwt.io/

pnpm i @nestjs/passport passport-jwt @nestjs/jwt @types/passport-jwt

百度搜索->在线密码生成器

//auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';

@Module({
  imports: [
    PassportModule,
    JwtModule.register({
      secret: 'RQ9u7x!F7yzsJLw4zhA97xuhVwxz5S#@',
    }),
  ],
  providers: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}

//这里支持异步导入secret
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        console.log(configService.get('JWT_SECRET'));

        return {
          secret: configService.get('JWT_SECRET'),
          signOptions: {
            expiresIn: configService.get('JWT_EXPIRATION_TIME'),
          },
        };
      },
    }),

创建auth.strategy.ts文件

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  //这里使用protected而不是private
  constructor(protected configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_SECRET'),
    });
  }

  // 验证用户
  async validate(payload: any) {
    return { id: payload.id, username: payload.username };
  }
}
//auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
  constructor(private jwtService: JwtService) {}
  signin(username: string) {
    //传入的参数取决于auth.strategy.ts中使用的参数
    return this.jwtService.signAsync({
      username,
      id: 1,
    });
  }

  signup() {
    return 'AuthService_signup';
  }
}
ignup';
  }
}

此时,即可通过管道添加jwt验证

//classify.controller.ts
import {
  Controller,
  Get,
  UseGuards,
  Req,
} from '@nestjs/common';
import { ClassifyService } from './classify.service';
import { CreateClassifyDto } from './dto/create-classify.dto';
import { AuthGuard } from '@nestjs/passport';

@Controller('classify')
@UseGuards(AuthGuard('jwt'))//添加验证管道
export class ClassifyController {
  constructor(private readonly classifyService: ClassifyService) {}

  @Get()
  findAll(@Req() req: any) {
    console.log(req.user);//也可以从req中获取user信息
    return this.classifyService.findAll();
  }
}

密码加密

使用argon2进行密码加密

pnpm i argon2
import * as argon2 from 'argon2'

Helmet、限速

pnpm i --save helmet @nestjs/throttler

helmet比较简单,直接在main中引入即可

...
import helmet from 'helmet';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(helmet());
   ...
}
bootstrap();

throttler比较坑,官网并没有提及APP_GUARD在哪引入

//app.module.ts
...
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [
...
    ThrottlerModule.forRoot([
      {
        ttl: 60000 * 15,
        limit: 50,
      },
    ]),
...
  ],
  controllers: [],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值