NestJs 从入门到实战项目笔记

controller ​控制

用于处理路由请求

路由的三种参数 Param Query Body

Param: restful API 参数

Query:url 参数

Body:post 参数

Param 就是获取 /xxx/:id

ts 复制代码
@Get('/data/:id')
getData(@Param() param): string {
  console.log('🚀 ~ AppController ~ getData ~ param:', param)
  return 'data' + param.id
}
​
@Controller('user')
export class UserController {
  @Get('/:id')
  // param 传递 id 使用 nest 自带的管道进行数据转换
  getUser(@Param('id', ParseIntPipe) id: number) {
    console.log('🚀 ~ UserController ~ id:', typeof id)
​
    return 'get user' + id
  }
}
​

Body

ts 复制代码
  @Post('/data')
  postData(@Body() body): string {
    console.log('🚀 ~ AppController ~ postData ~ body:', body)
    return 'post data'
  }

Query /xxx/xxx?id=xxx&param=222

ts 复制代码
  @Get('/data/:id')
  getData(@Param() param, @Query() query): string {
    console.log('🚀 ~ AppController ~ getData ~ param:', param)
    console.log('🚀 ~ AppController ~ getData ~ query:', query)
    return 'data' + param.id
  }

providers 提供函数方法

就是提供方法 哪里需要使用这个方法就在哪里进行注入

其实拿 koa2 来进行举例就是

controller ​是进行路由对接的 然后执行具体的函数方法 是在 service 里面执行的 数据库的修改就是进一层的里

ts 复制代码
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
​
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService], // 将service方法进行提供注入到这个module这样这个module就能全局使用
})
export class AppModule {}
​
ts 复制代码
  @Get('/data/:id')
  getData(@Param() param): string {
    return this.appService.getData(param)
  }
ts 复制代码
import { Injectable } from '@nestjs/common'
​
@Injectable()
export class AppService {
  getData(param: any) {
    console.log('🚀 ~ AppService ~ getData ~ param:', param)
    return 'data' + param.id
  }
}
​

异常处理器

ts 复制代码
// \src\common\exception\http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'
import { Request, Response } from 'express'
​
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()
    const status = exception.getStatus()
​
    response.status(status).json({
      statusCode: status,
      message: exception.message,
      type: 'error',
      timestamp: new Date().toISOString(),
      path: request.url,
    })
  }
}

main.ts

ts 复制代码
​
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { HttpExceptionFilter } from './common/exception/http-exception.filter'
import { VersioningType } from '@nestjs/common'
​
async function bootstrap() {
  const app = await NestFactory.create(AppModule)
​
  app.setGlobalPrefix('api') // 定义全局抬头
  app.enableVersioning({ type: VersioningType.URI, defaultVersion: '1' }) // 定义版本 /api/v1/xxx
​
  app.useGlobalFilters(new HttpExceptionFilter()) // 设置全局错误拦截器
​
  await app.listen(3000)
}
bootstrap().catch((err) => console.error(err))
​

config 配置文件

安装配置依赖

sql 复制代码
pnpm add @nestjs/config

根目录下创建配置文件 .env (该文件不进行提交) 我们创建.env.example 进行提交到代码仓库

.env

bash 复制代码
APP_NAME=nest-admin 
​
# 数据库配置
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=
DB_PASSWORD=
DB_DATABASE=
DB_SYNCHRONIZE=false

创建config模块文件

./common/config

ts 复制代码
// app.config.ts
import { registerAs } from '@nestjs/config'
​
export default registerAs('app', () => {
  return {
    name: process.env.APP_NAME,
    // port: 3000,
  }
})
​
ts 复制代码
// db.config.ts
​
import { registerAs } from '@nestjs/config'
​
export default registerAs('database', () => {
  return {
    type: process.env.DB_TYPE || 'mysql',
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT, 10) || 3306,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_DATABASE,
    synchronize: process.env.DB_SYNCHRONIZE === 'true',
  }
})
​
​
ts 复制代码
// index.ts 导出
​
import appConfig from './app.config'
import dbConfig from './db.config'
​
export default [appConfig, dbConfig]
​

再app.module.ts 进行导入使用

ts 复制代码
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { UserModule } from './modules/user/user.module'
import { AuthController } from './modules/auth/auth.controller'
import { AuthModule } from './modules/auth/auth.module'
import { ConfigModule } from '@nestjs/config'
import config from './common/config' // 导出的文件引入
​
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [...config], // 加载到全局
    }),
    UserModule,
    AuthModule,
  ],
  controllers: [AppController, AuthController],
  providers: [AppService],
})
export class AppModule {}
​

再具体的模块之中进行使用

ts 复制代码
import { Controller, Get, Inject } from '@nestjs/common'
import { ConfigType } from '@nestjs/config'
import appConfig from './common/config/app.config'
​
@Controller()
export class AppController {
  constructor(
    private readonly appService: AppService,
     // 使用接收全局的注入 然后使用类型工具进行包裹 这样就会有类型提示
    @Inject(appConfig.KEY) private readonly app: ConfigType<typeof appConfig>,
  ) {}
​
  @Get('/test')
  getTest(): string {
    console.log('🚀 ~ AppController ~ getTest ~ this.app.name:', this.app)
    return this.app.name
  }
​
}
​

连接数据库与操作流程

mysql数据库创建

bash 复制代码
数据库名: admin-dev
字符集: utf8
排序规则: utf8 general ci
css 复制代码
pnpm install --save @nestjs/typeorm typeorm mysql2
  • 连接数据库
ts 复制代码
// app.module.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { UserModule } from './modules/user/user.module'
import { AuthController } from './modules/auth/auth.controller'
import { AuthModule } from './modules/auth/auth.module'
import { ConfigModule, ConfigService } from '@nestjs/config'
import config from './common/config'
import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm'
​
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [...config],
    }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) =>
        ({
          type: configService.get('database.type'),
          host: configService.get('database.host'),
          port: configService.get('database.port'),
          username: configService.get('database.username'),
          password: configService.get('database.password'),
          database: configService.get('database.database'),
          synchronize: configService.get('database.synchronize'), // 是否自动同步 建议关闭 生产环境绝对不能使用
          autoLoadEntities: true, // 自动加载实体
        }) as TypeOrmModuleOptions,
    }),
    UserModule,
    AuthModule,
  ],
  controllers: [AppController, AuthController],
  providers: [AppService],
})
export class AppModule {}
​
  • 定义数据模型
ts 复制代码
// user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'
​
@Entity('admin_user')
export class User {
  @PrimaryGeneratedColumn() //主键并且自增
  id: number
​
  @Column()
  @Unique(['username'])
  username: string
​
  @Column()
  password: string
​
  @Column()
  role: string
​
  @Column()
  nickname: string
​
  @Column()
  active: boolean
}
​
  • 当前哪一个module需要使用就引入 实体
ts 复制代码
// user.module.ts
import { Module } from '@nestjs/common'
import { UserController } from './user.controller'
import { TypeOrmModule } from '@nestjs/typeorm'
import { User } from './user.entity'
import { UserService } from './user.service'
​
@Module({
  imports: [TypeOrmModule.forFeature([User])], // 引入需要使用的数据模型(实体)
  controllers: [UserController],
  providers: [UserService], // 再Service使用具体的 数据模型操作数据库数据
})
export class UserModule {}
​
  • 再具体模块的service进行实体使用
ts 复制代码
// userService
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { User } from './user.entity'
import { Repository } from 'typeorm'
​
@Injectable()
export class UserService {
  constructor(
    // 进行注解加上实体 使用实例来进行使用
    @InjectRepository(User)
    private readonly userRepository: Repository<User>, // 定义类型
  ) {}
​
  findOne(id: number) {
    // 执行具体的sql语句查询 
    return this.userRepository.findOne({ where: { id } })
  }
}
​
  • service 查询然后返回给controller进行使用
ts 复制代码
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'
import { UserService } from './user.service'
​
@Controller('user')
export class UserController {
   // 将逻辑返回给 controller 进行使用 
  constructor(private readonly userService: UserService) {}
​
  @Get('/:id')
  // param 传递 id 使用 nest 自带的管道进行数据转换
  getUser(@Param('id', ParseIntPipe) id: number) {
    return this.userService.findOne(id)
  }
}
​

守卫/全局守卫

bash 复制代码
​
nest g gu modules/auth
ts 复制代码
// auth.module.ts
import { Module } from '@nestjs/common'
import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'
import { APP_GUARD } from '@nestjs/core'
import { AuthGuard } from './auth.guard'
​
@Module({
  controllers: [AuthController],
  providers: [
    AuthService,
    {
      // 定义全局守卫
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AuthModule {}
​

如果只是定义单个的守卫 那么就不需要再 这个module里面写任何东西 只需要创建一个守卫 然后哪里需要哪里注入即可

这里我们注册的是全局路由守卫 就是每一个路由请求都会进入到这个守卫里面

  • 定义一个聚合装饰器,将标记公共装饰器和守卫装饰器聚合
ts 复制代码
// auth.decorator.ts
import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'
import { AuthGuard } from './auth.guard'
​
export const IS_PUBLIC_KEY = 'isPublic'
export function Auth() {
  //  这是聚合装饰器 表示可以聚合多个装饰器 这样外部只需要调用一个装饰器即可
  // 第一个装饰器作用是设置元数据 表示这是一个公共路由
  // 第二个装饰器作用是使用全局守卫 表示需要进行认证
  return applyDecorators(SetMetadata(IS_PUBLIC_KEY, true), UseGuards(AuthGuard))
}
​
/**
这里第一个装饰器 设置了 当前路由为公共路由,
然后我们再第一个装饰器就去判断是否设置为了公共路由设置了就会放行没有就会进行第二步判断
*/
  • 全局守卫(基础版本) 固定写法课参照文档全局守卫
ts 复制代码
// auth.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Observable } from 'rxjs'
import { IS_PUBLIC_KEY } from './auth.decorator'
​
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private reflector: Reflector) {}
​
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ])
​
    // 如果是公共路由,直接返回 true 不会被全局守卫拦截
    if (isPublic) {
      return true
    }
    return false
  }
}
​

使用

ts 复制代码
// auth.service.ts
​
import { Controller, Post } from '@nestjs/common'
import { Auth } from './auth.decorator'
​
@Controller('auth')
export class AuthController {
  @Auth()  // 使用装饰器
  @Post('login')
  login() {
    return 'login'
  }
}
​
相关推荐
蓝-萧8 小时前
Spring Security安全框架原理与实战
java·后端
景彡先生8 小时前
Python Flask详解:从入门到实战,轻量级Web框架的魅力
前端·python·flask
qq_420362038 小时前
AI在前端工作中的应用
前端·人工智能·sse
Lsx_8 小时前
详解ECharts中的convertToPixel和convertFromPixel
前端·javascript·echarts
吃饺子不吃馅8 小时前
Web端PPT应用画布方案:Canvas 还是 DOM?
前端·架构·canvas
晴殇i8 小时前
Web端PDF预览方法详解
前端·javascript·vue.js
SimonKing8 小时前
聊聊Spring里那个不打扰Controller就能统一改响应的“神器”
java·后端·程序员
加油乐8 小时前
解决 iOS 端输入框聚焦时页面上移问题
前端·javascript·ios
鹏多多8 小时前
纯前端提取图片颜色插件Color-Thief教学+实战完整指南
前端·javascript·vue.js