全局安装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配置
-
安装config pnpm i config
-
在根目录创建config文件夹
-
分别创建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-transformer
与class-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控制器
-
装饰器的执行顺序:方法的装饰器如果有多个,则是从下往上执行
-
如果一个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 {}