nest项目搭建过程(一)

252 阅读8分钟

项目初始化

全局安装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

image.png

配置数据库模块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
配置表实体

blog.csdn.net/qq119556631…

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

image.png


image.png

注入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

image.png

思考:正常情况下后端返回数据类型应该是一个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结果如下:

image.png

filter异常过滤器

Nest提供了一个内置的 HttpException 类,它从 @nestjs/common 包中导入。 HttpStatus 它是从 @nestjs/common 包导入的辅助枚举器。

使用方法
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';

.....
@Get() 
async findAll() { 
    throw new HttpException('报错信息', HttpStatus.FORBIDDEN); 
}

swagger测试

image.png

登录及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),
    };
  }
}
测试

image.png

非本地身份验证策略

创建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后续操作如下图:

image.png