nestjs学习日记1

98 阅读5分钟

安装

  • npm install -g @nestjs/cli
  • yarn add @nestjs/cli -g

基础

nest -help 查看快速生成nest代码片段的命令

  • nest g co 快速生成controller,会自动注册到@Module
  • nest g co --no-spec 不生成spec测试文件。 可以在nest-cli.json文件里配置generateOptions:{"spec":false}这样命令无需加上--no-spec也不会生成spec测试文件
  • npm test 运行src下所有的spec文件,针对controller生成测试文件

@Body('xx') xx:any body注解接收json参数

/* 
1. 在main.ts根module创建数据库的链接
2. 在业务子module创建映射关系
 */
 
 //根module
 @Module({
  imports: [UserModule,TypeOrmModule.forRoot({
    host: 'www.sqlpub.com',
    username: 'dora_learn',
    password: 'bnAR5BFEC0dAmajG',
    database: 'dora_learn',
    port: 3306,
    type: 'mysql',
    entities: [__dirname + '/**/*.entity{.ts,.js}'],
    synchronize: true,
  }), StudentModule],// 所有子模块都定义在根模块上
  controllers: [AppController, StudentController],
  providers: [AppService],
})
 
//业务子module
@Module({
  imports: [TypeOrmModule.forFeature([/* entities here */])],//创建映射关系
  providers: [StudentService],
  controllers:[StudentController]
})

插件:REST Client 定义文件直接发起请求,方便测试接口

1.GraphQL

  • nest g res 生成一个GraphQL类型的文件夹包含dto,entities、resolver、service
resolver中:
 @Query(() => [Book], { name: 'findAllBooks' })
  findAll() {
    return this.booksService.findAll();
  }
 @Query(() => String, { name: 'book' })
  findOne(@Args('id', { type: () => Int }) id: number) {
    return this.booksService.findOne(id);
  }


❎ query {
  book(id:27){
    String
  }
} 
✅ query {
  book(id:27)
}

GraphQL 查询不需要指定返回类型为 String,因为这是在 Schema 定义时已经指定的。你只需要调用查询名和传递所需的参数即可。 如果你的查询报错,可能是因为你的查询结构与 Schema 不匹配,或者服务器端的解析器未正确处理该查询。务必确保你的客户端查询与服务端的 GraphQL Schema 中定义的类型和结构一致。

注意点:

  • 不能通过直接输入url请求GraphQL

在 Web 开发中,浏览器直接通过地址栏输入 URL 发起的请求默认是 GET 方法。但是,GraphQL API 通常是通过 HTTP POST 方法来接收请求的,因为 GraphQL 查询和变更(mutations)通常会包含复杂的请求体(payload),而 GET 请求通常不会包含请求体。

此外,GraphQL 的设计理念是通过一个单一的端点接收所有的查询和变更请求。这意味着,对于 GraphQL API,你不会有多个 URL 对应不同的操作或资源,而是有一个统一的 URL 端点(如 /graphql),所有的操作都通过这个端点的请求体中传递的查询语句来区分。

这就是为什么你不能像调用 REST API 那样直接在浏览器地址栏输入 URL 来调用 GraphQL API。GraphQL 需要接收到完整的查询语句,这在地址栏中无法实现,因为地址栏限制了可以发送的数据类型和长度。

为了在客户端(如前端应用、Postman、curl 等)调用 GraphQL API,你需要构建一个 HTTP POST 请求,并将 GraphQL 查询作为请求体发送到服务器的 /graphql 端点。这通常通过编程方式完成,使用如 JavaScript 的 fetch API、axios 库、Apollo Client 或其他 HTTP 客户端库。

但是,有些 GraphQL 服务器实现可能支持通过 GET 请求来执行简单的查询。在这种情况下,查询需要被序列化为 URL 的查询参数。这通常用于缓存目的或非常简单的查询,但不是 GraphQL 的主要使用方式。即使是 GET 请求,它们也通常需要通过工具来构建,因为 URL 中需要包含序列化后的查询字符串,这在手动构建时可能会很复杂。

  • 前端通过axios等库请求

import axios from 'axios';

const query = query { findAllBooks { // 在此处填入你想要返回的 Book 对象的字段 id title author // 其他你需要的字段... } };

axios.post('http://localhost:3000/graphql', { query: query }) .then(response => { console.log(response.data); }) .catch(error => { console.error('Error fetching data: ', error); });

使用 Apollo Client (专为 GraphQL 设计的库)

import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({ uri: 'http://localhost:3000/graphql', cache: new InMemoryCache(), });

const GET_ALL_BOOKS = gql query { findAllBooks { // 在此处填入你想要返回的 Book 对象的字段 id title author // 其他你需要的字段... } };

client.query({ query: GET_ALL_BOOKS }) .then(response => console.log(response.data)) .catch(error => console.error('Error fetching data: ', error));

  • 通过postman等工具发起请求

请求 http://localhost:3000/graphql

选择post方法,body->row:

{ "query": "query { findAllBooks { id title author } }" }

注意:Apollo Server 4 开始增加了 CSRF 防护特性,要求客户端在发送 GraphQL 请求时遵守特定的标准: Header头部要设置Content-Type:application/json

2. 异常过滤器

快速创建一个过滤器 nest g f xxx过滤器名称 --no-spec --flat

src下生成xxx过滤器名称.filter.ts 文件

全局使用:

  • 在main.ts中使用app.useGlobalFilters进行注册
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception.filter';
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();
  • 在app.module注入
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './http-exception.filter';
@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
  export class AppModule {}

单个控制器中使用

@Controller('user')
export class UserController {
    constructor(private readonly userService:UserService){}
    @Get('/getUser')
    @UseFilters(HttpExceptionFilter)
    getUser():string{
        throw new ForbiddenException('用户不存在')
        // return this.userService.getUser()
    }
}

自定义异常:处理业务异常


export enum CustomErrorCode {
  USER_NOTEXIST = 10002, // 用户不存在
  USER_EXIST = 10003, //用户已存在
}
export class CustomException extends HttpException {
  private errorMessage: string;
  private errorCode: CustomErrorCode;

  constructor(
    errorMessage: string,
    errorCode: CustomErrorCode,
    statusCode: HttpStatus = HttpStatus.OK,
  ) {
    super(errorMessage, statusCode);
    this.errorMessage = errorMessage;
    this.errorCode = errorCode;
  }

  getErrorCode(): CustomErrorCode {
    return this.errorCode;
  }

  getErrorMessage(): string {
    return this.errorMessage;
  }
}

这里实现了一个CustomException类继承HttpException类,接收三个参数:错误信息,业务异常码以及http错误码(默认为200,一般业务异常http请求通常是正常的)。同时提供了获取错误码及错误信息的函数。同时导出了一个自定义错误码的枚举

然后我们来到异常过滤器中http-exception.filter.ts做一些逻辑处理

import { ArgumentsHost, Catch, ExceptionFilter,HttpException, NotFoundException } from '@nestjs/common';
import { CustomException } from './custom.exception';

@Catch(HttpException,NotFoundException)
export class HttpExceptionFilter<T> implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取请求上下文
    const response = ctx.getResponse(); // 获取响应对象
    const request = ctx.getRequest(); // 获取请求对象
    const status = exception.getStatus(); // 获取异常的状态码
    
    //判断是否为自定义类
    if (exception instanceof CustomException) {
      response.status(status).json({
        statusCode: exception.getErrorCode(),
        message: exception.getErrorMessage(),
        timestamp: new Date().toISOString(),
        path: request.url,
        test:'自定义业务异常:用户异常'
      });
      return;
    }

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message:'自定义异常抛出'
    }); 
  }
}


这里通过判断是否是通过自定义异常抛出的错误来返回不同的结果,然后我们在控制层抛出一个自定义错误看一下

import { Controller, ForbiddenException, Get, UseFilters } from '@nestjs/common';
import { UserService } from './user.service';
import { HttpExceptionFilter } from 'src/http-exception.filter';
import { CustomErrorCode, CustomException } from 'src/custom.exception';
// common里存放了各种各样的请求协议

@Controller('user')
export class UserController {
    constructor(private readonly userService:UserService){}
    @Get('/getUser')
    @UseFilters(HttpExceptionFilter)
    getUser():string{
        // throw new ForbiddenException('用户不存在')
        // return this.userService.getUser()
        throw new CustomException('用户已存在',CustomErrorCode.USER_EXIST)
    }
}