项目初始化
全局安装nest
npm i nest -g
创建项目
nest new nestobj
打开项目安装依赖
code ./nestobj
npm i
运行项目
npm run start:dev
此时项目报错如下:
nestobj/node_modules/@types/estree/index"' has no exported member 'PrivateIdentifier'.
PrivateIdentifier?: ((node: ESTree.PrivateIdentifier & NodeParentExtension) => void) | undefined;
解决方法:
npm i @types/estree
此时 ‘http://localhost:3000/’ 可以正常访问
配置swagger
安装
首先,您必须安装所需的包:
npm install --save @nestjs/swagger swagger-ui-express
配置
安装过程完成后,打开引导文件(主要是 main.ts )并使用 SwaggerModule 类初始化 Swagger:
main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ApplicationModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
应用程序运行时,打开浏览器并导航到 http://localhost:3000/api 。 你应该可以看到 Swagger UI
配置数据库模块typeORM
安装
npm install --save typeorm mysql
配置
安装过程完成后,我们可以将 TypeOrmModule 导入AppModule 。
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [],
synchronize: true,
}),
],
})
export class AppModule {}
forRoot() 方法支持所有TypeORM包中createConnection()函数暴露出的配置属性。其他一些额外的配置参数描述如下:
| 参数 | 说明 |
|---|---|
| retryAttempts | 重试连接数据库的次数(默认:10) |
| retryDelay | 两次重试连接的间隔(ms)(默认:3000) |
| autoLoadEntities | 如果为true,将自动加载实体(默认:false) |
| keepConnectionAlive | 如果为true,在应用程序关闭后连接不会关闭(默认:false) |
另外,我们可以创建 ormconfig.json ,而不是将配置对象传递给 forRoot()。
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "root",
"database": "test",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}
然后,我们可以不带任何选项地调用 forRoot() :
(我本地使用此方法不生效,暂时先写在函数里)
app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
})
export class AppModule {}
如果出现以下报错
ERROR [ExceptionHandler] (0 , rxjs_1.lastValueFrom) is not a function
解决办法
npm i rxjs@^7
此时项目运行如下,只用配置好数据库信息即可
ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)...
创建user模块
运行命令
nest g res user
配置表实体
user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity() //入口标识
export class User {
// @PrimaryGeneratedColumn() //自增主键
@PrimaryGeneratedColumn('uuid') //自增主键uuid
id: number;
@Column()
username: string;
@Column()
password: string;
}
此时数据库会自动创建user表,表内字段就是user.entity.ts文件内配置的column
注入Repository(model文件)
使用 forFeature() 方法定义在当前范围中注册哪些存储库, 这样,我们就可以使用 @InjectRepository()装饰器将 UsersRepository 注入到 UsersService 中:
user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm'; //引入TypeOrmModule
import { User } from './entities/user.entity'; //引入实体
@Module({
controllers: [UserController],
providers: [UserService],
imports: [
/**
* 使用 forFeature() 方法定义在当前范围中注册哪些存储库,
* 这样,我们就可以使用 @InjectRepository()装饰器将 UsersRepository 注入到 UsersService 中:
*/
TypeOrmModule.forFeature([User]),
],
})
export class UserModule {}
在user.service.ts使用userRepository.find()查询user所有数据
user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
//注入userRepository,后边可以用this访问
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
create(createUserDto: CreateUserDto) {
return 'This action adds a new user';
}
findAll() {
//使用userRepository查询所有数据
return this.userRepository.find();
}
findOne(id: number) {
return `This action returns a #${id} user`;
}
update(id: number, updateUserDto: UpdateUserDto) {
return `This action updates a #${id} user`;
}
remove(id: number) {
return `This action removes a #${id} user`;
}
}
打开swagger测试GET /user得到response
思考:正常情况下后端返回数据类型应该是一个json格式的数据如下:
{
data:[],
code:200,
message:"请求成功!"
}
interceptor拦截器的使用
创建拦截器
在src下创建interceptors/transform.interceptor.ts
transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { error } from 'console';
import { Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { AnyError } from 'typeorm';
export interface Response<T> {
data: T;
}
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, Response<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler,
): Observable<Response<T>> {
return next.handle().pipe(
map((data) => ({ code: 200, data, message: '请求成功!' })),
catchError((error) => {
throw error;
}),
);
}
}
绑定拦截器
(局部)为了设置拦截器, 我们使用从 @nestjs/common 包导入的 @UseInterceptors() 装饰器,
拦截器可以是控制器范围内的, 方法范围内的或者全局范围内的。
例如:xxx.controller.ts
import { Controller, Get } from '@nestjs/common';
import { XxxService } from './Xxx.service';
import { UseInterceptors } from '@nestjs/common';
import { TransformInterceptor } from './interceptors/transform.interceptor';
@UseInterceptors(TransformInterceptor)
@Controller()
export class XxxController {
constructor(private readonly xxxService: XxxService) {}
@Get()
index(): string {
return this.xxxService.find();
}
}
(全局)为了绑定全局拦截器, 我们使用 Nest 应用程序实例的 useGlobalInterceptors() 方法:
const app = await NestFactory.create(ApplicationModule);
app.useGlobalInterceptors(new LoggingInterceptor());
可忽略================================start
全局拦截器用于整个应用程序、每个控制器和每个路由处理程序。在依赖注入方面, 从任何模块外部注册的全局拦截器 无法插入依赖项, 因为它们不属于任何模块。为了解决此问题, 您可以使用以下构造直接从任何模块设置一个拦截器:
例如:app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
//数据库模块
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './user/user.module';
//拦截器注入
import { TransformInterceptor } from './interceptors/transform.interceptor';
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
imports: [
//配置数据库信息
TypeOrmModule.forRoot({
type: 'mysql',
host: 'xxxx',
port: 3306,
username: 'xxxx',
password: 'xxxx',
database: 'xxxx',
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: true,
}),
UserModule,
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
},
],
})
export class AppModule {}
可忽略================================end
测试
此时访问swagger结果如下:
filter异常过滤器
Nest提供了一个内置的HttpException类,它从@nestjs/common包中导入。HttpStatus它是从@nestjs/common包导入的辅助枚举器。
使用方法
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
.....
@Get()
async findAll() {
throw new HttpException('报错信息', HttpStatus.FORBIDDEN);
}
swagger测试
登录及token认证
根据账号密码查询用户表是否存在,存在的话生成token,不存在抛出异常即可
生成一个 AuthModule
nest g res auth
npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
在 UserService 中封装用户操作
user/user.service.ts
findOne(data) {
return this.userRepository.findOne({ where: { ...data } });
}
暴露UserService
在 UsersModule 中,将 UserService 添加到 @Module 装饰器的 exports 数组中,以便提供给其他模块外部可见(我们将在 AuthService 中使用它)。
user/user.module.ts
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm'; //引入TypeOrmModule
import { User } from './entities/user.entity'; //引入实体
@Module({
controllers: [UserController],
providers: [UserService],
imports: [
/**
* 使用 forFeature() 方法定义在当前范围中注册哪些存储库,
* 这样,我们就可以使用 @InjectRepository()装饰器将 UsersRepository 注入到 UsersService 中:
*/
TypeOrmModule.forFeature([User]),
],
exports: [UserService],
})
export class UserModule {}
我们的 AuthService 的任务是检索用户并验证密码。为此,我们创建了 validateUser() 方法。
auth/auth.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { UserService } from '../user/user.service';
@Injectable()
export class AuthService {
constructor(private readonly userService: UserService) {}
async validateUser(userData) {
const { username, password } = userData;
if (username && password) {
const res = await this.userService.findOne({
username,
password,
});
if (res) {
return res;
} else {
throw new HttpException('用户名密码不正确!', HttpStatus.FORBIDDEN);
}
} else {
throw new HttpException('请输入用户名、密码!', HttpStatus.FORBIDDEN);
}
}
}
此时在AuthService中还无法使用UserService,更新 AuthModule 来导入 UsersModule
auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from '../user/user.module';
@Module({
controllers: [AuthController],
providers: [AuthService],
imports:[UserModule]
})
export class AuthModule {}
本地身份验证策略
现在我们可以实现 Passport 本地身份验证策略。在 auth 文件夹中创建一个名为 local.strategy.ts 文件,并添加以下代码:
auth/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
我们需要配置 AuthModule 来使用刚才定义的 Passport 特性。更新 auth.module。
auth/auth.module.ts
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
@Module({
controllers: [AuthController],
providers: [AuthService,LocalStrategy],
imports: [UserModule,PassportModule],
})
export class AuthModule {}
登陆路由
auth/auth.controller.ts
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@UseGuards(AuthGuard('local'))
@Post('/login')
login(@Body() userData) {
return this.authService.validateUser(userData);
}
}
对于 @UseGuards(AuthGuard('local')),我们的 Passport 本地策略默认名为"local" 。我们在 @UseGuards() 装饰器中引用这个名。在应用程序中有多个 Passport 策略时,会存在歧义,所以我们把AuthGuard('local')封装起来。我们创建自己的类,如下所示:
auth/local-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
// 使用时=== @UseGuards(LocalAuthGuard)=== @UseGuards(AuthGuard('local'))
JWT功能
npm install @nestjs/jwt passport-jwt
npm install @types/passport-jwt --save-dev
在auth.service.ts中引入JwtService并添加 login() 方法
auth.service.ts
login(userData) {
const payload = { username: userData.username, sub: userData.id };
return {
access_token: this.jwtService.sign(payload),
};
}
我们使用 @nestjs/jwt 提供的sign() 函数一个 access_token 。注意:我们选择 sub 的属性名来保持我们的 userId 值与JWT 标准一致。不要忘记将 JwtService 提供者注入到 AuthService中。
需要更新 AuthModule 来导入新的依赖项并配置 JwtModule 。
配置秘钥
首先,在 auth 文件夹下创建 auth/constants.ts,并添加以下代码:
constants.ts
export const jwtConstants = {
secret: 'secretKey',
};
配置auth.module.ts
@Module({
controllers: [AuthController],
providers: [AuthService, LocalStrategy],
imports: [
UserModule,
PassportModule,
JwtModule.register({
secret: jwtConstants.secret,
signOptions: {
expiresIn: '60s',
},
}),
],
})
更新 /auth/login 路径来返回 JWT
auth.controller.ts
@Post('/login')
login(@Body() userData) {
return this.authService.login(userData);
}
auth.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}
async validateUser(userData) {
const { username, password } = userData;
if (username && password) {
const res = await this.userService.findOne({
username,
password,
});
if (res) {
return res;
} else {
throw new HttpException('用户名密码不正确!', HttpStatus.FORBIDDEN);
}
} else {
throw new HttpException('请输入用户名、密码!', HttpStatus.FORBIDDEN);
}
}
async login(userData) {
const res = await this.validateUser(userData);
const payload = { username: res.username, sub: res.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
测试
非本地身份验证策略
创建auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
在 AuthModule 中添加新的 JwtStrategy
auth/auth.module.ts
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
@Module({
providers: [AuthService, LocalStrategy,JwtStrategy],
})
在需要使用的路径上
import { LocalAuthGuard, JwtAuthGuard } from './local-auth.guard';
import { AuthDto } from './dto/auth.dto';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@UseGuards(LocalAuthGuard)
@Post('/login')
login(@Body() userData: AuthDto) {
return this.authService.login(userData);
}
@UseGuards(JwtAuthGuard)
@Get()
test() {
return '这个接口需要token';
}
}
此时在请求上添加请求头Authorization 值为Bearer+空格+token即可通过验证
关于Bearer为token验证类型,一种固定格式
###token测试
调取登陆接口获取token后续操作如下图: