Nest基本概念 - 路由和 HTTP 请求处理

7/11/2023 NodejsNestjs

在本章中,我们将深入探讨 NestJS 中的路由和 HTTP 请求处理。路由是应用程序中定义 URL 和请求方法之间关系的方式,而 HTTP 请求处理涉及到如何处理不同类型的请求并返回适当的响应。

# 3.1 路由的基本概念

在 NestJS 中,路由是通过控制器和模块来定义的。每个控制器负责处理特定的 URL 路径,并且可以包含多个路由处理器,每个路由处理器负责处理不同类型的 HTTP 请求方法,如 GET、POST、PUT 等。

# 定义路由

在 NestJS 中,我们使用装饰器来定义路由。在控制器类中,我们可以使用装饰器如 @Get()、@Post() 等来指定对应的 HTTP 请求方法,并将其绑定到特定的 URL 路径上。

例如,假设我们有一个控制器类 CatsController,我们可以在其中定义以下路由:

import { Controller, Get, Post, Put, Delete } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }

  @Post()
  create(): string {
    return 'This action creates a new cat';
  }

  @Put()
  update(): string {
    return 'This action updates a cat';
  }

  @Delete()
  remove(): string {
    return 'This action removes a cat';
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

在上面的代码中,我们使用了 @Get()、@Post()、@Put() 和 @Delete() 装饰器来定义四个不同的路由处理器,并将它们分别绑定到 /cats 路径上。

# 带参数的路由

在实际应用中,我们可能需要在路由中使用参数来处理不同的情况。在 NestJS 中,我们可以使用 : 来定义参数,然后在路由处理器中通过 @Param() 装饰器来获取参数的值。

例如,我们可以修改 CatsController 类,定义一个带参数的路由:

import { Controller, Get, Param } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string): string {
    return `This action returns a cat with id: ${id}`;
  }
}
1
2
3
4
5
6
7
8
9

在上面的代码中,我们使用了 :id 来定义一个参数,然后在 findOne 方法中使用 @Param('id') 来获取该参数的值。当我们发送 GET 请求到 /cats/123 路径时,findOne 方法会被调用,并将返回 "This action returns a cat with id: 123"。

# 3.2 路由模块化

在大型应用程序中,随着路由数量的增加,将所有路由都定义在一个控制器中可能会变得混乱和不易维护。因此,NestJS 允许我们将路由模块化,将不同功能模块的路由分组在一起。

创建路由模块 要创建路由模块,我们需要使用 @Module() 装饰器来定义一个模块类,并在其中使用 @Controller() 装饰器来定义控制器类。

例如,我们可以创建一个名为 CatsModule 的路由模块,其中包含 CatsController:

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';

@Module({
  controllers: [CatsController],
})
export class CatsModule {}
1
2
3
4
5
6
7

在上面的代码中,我们创建了一个名为 CatsModule 的路由模块,并将 CatsController 添加到了模块的 controllers 数组中。

# 引入路由模块

为了让路由模块生效,我们需要将它引入到根模块或其他模块中。

例如,我们可以将 CatsModule 引入到根模块 AppModule:

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule {}
1
2
3
4
5
6
7

在上面的代码中,我们通过 imports 属性将 CatsModule 添加到了根模块 AppModule 中,这样 CatsModule 中的路由就成为了整个应用程序的一部分,并可以在其他模块中使用。

# 3.3 路由守卫

路由守卫用于保护路由,实现对请求的预处理和拦截。它可以用于验证身份、权限检查、日志记录等操作。

# 创建路由守卫

要创建路由守卫,我们需要实现 CanActivate 接口,并在守卫类中定义 canActivate 方法。

例如,我们创建一个名为 AuthGuard 的路由守卫

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    // 在实际应用中,进行身份验证和权限检查逻辑
    const isAuthenticated = true; // 假设这里是身份验证的结果
    if (!isAuthenticated) {
      // 如果未通过身份验证,则拒绝访问路由
      return false;
    }
    return true; // 允许访问路由
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在上面的代码中,我们创建了一个名为 AuthGuard 的路由守卫,实现了 CanActivate 接口,并在 canActivate 方法中进行了身份验证和权限检查的逻辑。在这个例子中,我们假设 isAuthenticated 表示是否通过身份验证,如果未通过身份验证,则拒绝访问路由,否则允许访问路由。

# 使用路由守卫

要使用路由守卫,我们可以通过 @UseGuards() 装饰器将其应用于控制器中的特定路由或全局路由。

例如,我们将 AuthGuard 应用于 CatsController 中的一个路由:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';

@Controller('cats')
export class CatsController {
  @Get()
  @UseGuards(AuthGuard)
  findAll(): string {
    return 'This action returns all cats';
  }
}
1
2
3
4
5
6
7
8
9
10
11

在上面的代码中,我们在 findAll 方法上使用了 @UseGuards(AuthGuard) 装饰器,将 AuthGuard 应用于该路由。这样,在发送 GET 请求到 /cats 路径时,AuthGuard 将先进行身份验证和权限检查,如果通过则允许访问路由,否则拒绝访问路由。

# 3.4 路由拦截器

路由拦截器用于对请求、响应和异常进行全局的处理。它可以在请求到达控制器之前或响应离开控制器之后对它们进行拦截。

# 创建路由拦截器

要创建路由拦截器,我们需要实现 NestInterceptor 接口,并在拦截器类中定义 intercept 方法。

例如,我们创建一个名为 LoggingInterceptor 的路由拦截器:

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('Before...');
    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`))
      );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在上面的代码中,我们创建了一个名为 LoggingInterceptor 的路由拦截器,实现了 NestInterceptor 接口,并在 intercept 方法中进行了请求拦截和响应拦截的逻辑。在这个例子中,我们在请求到达控制器之前打印 "Before...",在响应离开控制器之后打印 "After...",并输出请求处理的时间。

# 使用路由拦截器

要使用路由拦截器,我们可以通过 @UseInterceptors() 装饰器将其应用于控制器中的特定路由或全局路由。

例如,我们将 LoggingInterceptor 应用于 CatsController 中的一个路由:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';

@Controller('cats')
export class CatsController {
  @Get()
  @UseInterceptors(LoggingInterceptor)
  findAll(): string {
    return 'This action returns all cats';
  }
}
1
2
3
4
5
6
7
8
9
10
11

在上面的代码中,我们在 findAll 方法上使用了 @UseInterceptors(LoggingInterceptor) 装饰器,将 LoggingInterceptor 应用于该路由。这样,在发送 GET 请求到 /cats 路径时,LoggingInterceptor 将分别打印 "Before..." 和 "After..."。

# 3.5 请求处理

在 NestJS 中,我们可以通过装饰器来访问请求对象和响应对象,以及从请求中获取数据。

# 获取请求参数

要获取请求参数,我们可以使用 @Query() 装饰器来访问查询参数,使用 @Body() 装饰器来访问请求体参数,使用 @Param() 装饰器来访问路由参数。

例如,我们创建一个名为 CatsController 的控制器,并定义一个带参数的路由:

import { Controller, Get, Query, Post, Body, Param } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Query('limit') limit: number, @Query('page') page: number): string {
    return `This action returns all cats. Limit: ${limit}, Page: ${page}`;
  }

  @Post()
  create(@Body() createCatDto: any): string {
    return `This action creates a new cat with name: ${createCatDto.name}`;
  }

  @Get(':id')
  findOne(@Param('id') id: string): string {
    return `This action returns a cat with id: ${id}`;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在上面的代码中,我们使用 @Query('limit') 和 @Query('page') 来访问查询参数,使用 @Body() 来访问请求体参数,使用 @Param('id') 来访问路由参数。

当我们发送 GET 请求到 /cats?limit=10&page=1 路径时,findAll 方法会被调用,并将返回 "This action returns all cats. Limit: 10, Page: 1"。

当我们发送 POST 请求到 /cats 路径,并在请求体中包含以下 JSON 数据:

{
  "name": "Fluffy"
}
1
2
3

create 方法会被调用,并将返回 "This action creates a new cat with name: Fluffy"。

当我们发送 GET 请求到 /cats/123 路径时,findOne 方法会被调用,并将返回 "This action returns a cat with id: 123"。

# 返回响应

在 NestJS 中,我们可以使用 @Res() 装饰器来访问响应对象,并使用 response 对象的方法来设置响应。

例如,我们修改 CatsController 类中的 create 方法,将响应对象作为参数传递:

import { Controller, Post, Body, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto: any, @Res() response: Response): void {
    response.status(201).json({
      message: `Cat named ${createCatDto.name} has been created successfully`,
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

在上面的代码中,我们使用 @Res() 装饰器来访问响应对象,并使用 response.status(201).json() 来设置响应状态码为 201,并返回一个 JSON 对象作为响应体。

当我们发送 POST 请求到 /cats 路径,并在请求体中包含以下 JSON 数据:

{
  "name": "Fluffy"
}
1
2
3

create 方法会被调用,并返回以下 JSON 响应:

{
  "message": "Cat named Fluffy has been created successfully"
}
1
2
3

# 异常处理

在 NestJS 中,我们可以使用异常过滤器来全局处理异常,或者在控制器中使用 throw 关键字来抛出异常并让异常过滤器进行处理。

例如,我们修改 CatsController 类中的 findOne 方法,假设我们根据 id 查找不到对应的猫,则抛出一个自定义的 NotFoundException 异常:

import { Controller, Get, Param, NotFoundException } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get(':id')
  findOne(@Param('id') id: string): string {
    // 假设根据 id 查找不到对应的猫
    if (!catFound) {
      throw new NotFoundException(`Cat with id ${id} not found`);
    }
    return `This action returns a cat with id: ${id}`;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在上面的代码中,我们使用 throw new NotFoundException() 来抛出一个自定义的 NotFoundException 异常,并在异常消息中包含了无法找到的猫的 id。

当我们发送 GET 请求到 /cats/123 路径时,findOne 方法会被调用,但由于我们假设找不到对应的猫,所以会抛出 NotFoundException 异常。

在应用程序中,我们可以通过实现一个自定义的异常过滤器来处理 NotFoundException 异常,并返回合适的响应给客户端。这样,我们可以统一处理应用程序中的异常情况,并向客户端提供更友好的错误信息。

# 3.6 路由的其他功能

NestJS 提供了许多其他功能来处理路由,如路由模块化、路由前缀、路由重定向等。在实际应用中,我们可以根据需求来选择使用这些功能。

# 路由模块化

路由模块化允许我们将路由分组在一起,创建更易于维护和组织的路由模块。我们已经在前面的章节中介绍了路由模块化的概念和用法。

# 路由前缀

路由前缀允许我们在控制器上添加一个前缀,从而将一组路由统一添加到一个公共的路径下。这样,我们可以为一组路由添加相同的前缀,避免重复书写路径。

例如,我们在 CatsController 类上添加一个前缀 v1:

import { Controller, Get } from '@nestjs/common';

@Controller('v1/cats')
export class CatsController {
  @Get()
  findAll(): string {
    return 'This action returns all cats';
  }
}
1
2
3
4
5
6
7
8
9

在上面的代码中,我们在 @Controller() 装饰器中添加了前缀 v1/cats,这样所有 CatsController 中定义的路由都会添加到 /v1/cats 路径下。

# 路由重定向

路由重定向允许我们将一个 URL 重定向到另一个 URL,从而实现页面重定向的功能。

例如,我们在 CatsController 类中添加一个重定向路由:

import { Controller, Get, Redirect } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get('old-path')
  @Redirect('new-path', 301)
  redirectToNewPath(): void {}
}
1
2
3
4
5
6
7
8

在上面的代码中,我们使用 @Get('old-path') 来定义一个路由 /cats/old-path,然后使用 @Redirect('new-path', 301) 来将这个路由重定向到 /new-path 路径,并使用状态码 301 表示永久重定向。

当我们发送 GET 请求到 /cats/old-path 路径时,将会自动重定向到 /new-path 路径。

编辑时间: 7/11/2023, 10:00:00 AM