Tổng quan – NestJS

Middleware

Middleware là một hàm được gọi trước khi tới handler route. Các hàm middleware có quyền truy cập vào các object requestresponse cũng như hàm middleware next() trong chu trình request-response của ứng dụng. Hàm middlware next thường được ký hiệu bằng một biến có tên là next.

Nest middleware, theo mặc định, tương đương với express middleware. Mô tả sau đây từ tài liệu express chính thức mô tả các khả năng của middleware:

Các hàm Middleware có thể thực hiện các nhiệm vụ sau:

  • Thực thi bất kỳ mã nào.
  • Thực hiện các thay đổi đối với request và response object.
  • Kết thúc chu kỳ request-response.
  • Gọi hàm middleware tiếp theo trong ngăn xếp.
  • Nếu hàm middleware hiện tại không kết thúc chu kỳ request-response, nó phải gọi next() để chuyển quyền điều khiển cho hàm middleware tiếp theo. Nếu không, request sẽ bị treo.

Bạn triển khai middleware Nest tùy chỉnh trong một hàm hoặc trong một class với @Injectable() decorator. Lớp nên implement interface NestMiddleware, trong khi hàm này không có bất kỳ yêu cầu đặc biệt nào. Hãy bắt đầu bằng cách triển khai một tính năng middleware đơn giản bằng cách sử dụng phương thức lớp.

logger.middleware.ts

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}

Dependency injection

Middleware Nest hỗ trợ đầy đủ Dependency Injection. Cũng như với các provider và controller, họ có thể inject dependencies có sẵn trong cùng một mô-đun. Như thường lệ, điều này được thực hiện thông qua construstor.

Applying middleware

Không có chỗ cho middleware trong @Module() decorator. Thay vào đó, chúng tôi thiết lập chúng bằng phương thức configure() của lớp mô-đun. Các mô-đun bao gồm middleware phải triển khai interface NestModule. Hãy thiết lập LoggerMiddleware ở cấp AppModule.

app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('cats');
  }
}

Trong ví dụ trên, chúng ta đã thiết lập LoggerMiddleware cho các route handler /cats đã được xác định trước đó bên trong CatsController. Chúng tôi cũng có thể hạn chế thêm middleware đối với một phương thức yêu cầu cụ thể bằng cách chuyển một đối tượng chứa đường dẫn route và phương thức request đến phương thức forRoutes() khi định cấu hình middleware. Trong ví dụ dưới đây, lưu ý rằng chúng tôi nhập enum RequestMethod để tham chiếu kiểu phương thức request mong muốn.

app.module.ts

import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: 'cats', method: RequestMethod.GET });
  }
}

HINT Phương thức configure() có thể được thực hiện không đồng bộ bằng cách sử dụng async/await (e.g., bạn có thể await hoàn thành hoạt động không đồng bộ bên trong phần thân của phương thức configure()).

Route wildcards

Pattern dựa trên route cũng được hỗ trơ. Ví dụ, dấu sao (*) được sử dụng làm ký tự đại diện và sẽ khớp với bất kỳ tổ hợp ký tự nào:

forRoutes({ path: 'ab*cd', method: RequestMethod.ALL });

Đường dẫn route 'ab*cd' sẽ khớp với abcdab_cdabecd và vân vân. Những ký tự ?+*, và () có thể được sử dụng trong đường dẫn route, và là các tập con của các regular expression counterparts của chúng. Dấu gạch nối (-) và dấu chấm (.) được hiểu theo nghĩa đen bằng các đường dẫn dựa trên chuỗi.

WARNING Gói fastify sử dụng phiên bản mới nhất của gói đường dẫn path-to-regexp, gói này ko còn hỗ trợ dấu *. Thay vào đó bạn phải sử udngj các tham số (e.g., (.*):splat*).

Middleware consumer

MiddlewareConsumer là một class helper. Nó cung cấp một số phương pháp tích hợp middleware. Tất cả chúng có thể được xâu chuỗi 1 cách đơn giản trôi chảy. Phương thức forRoutes() có thể nhận được 1 chuỗi đơn, nhiều chuỗi, 1 đối tượng RouteInfo, 1 lớp controller và thậm chí nhiều lớp controller.Trong hầu hết các trường hợp, bạn có thể sẽ chỉ chuyển 1 danh sách các controller được phân tách bằng dấu phẩy. Dưới đây là 1 ví dụ với 1 controller duy nhất.

app.module.ts

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller.ts';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes(CatsController);
  }
}

HINT Phương thức apply() có thể lấy 1 middleware duy nhất hoặc nhiều đối số để chỉ định nhiều middlewares.

Excluding routes

Đôi khi chúng tôi muốn loại trừ một số route nhất định khỏi việc áp dụng middleware. Chúng ta có thể dễ dàng loại trừ các route nhất định bằng phương thức exclude(). Phương pháp này có thể lấy một chuỗi đơn, nhiều chuỗi hoặc một đối tượng RouteInfo xác định các route bị loại trừ, như được hiển thị bên dưới:

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: 'cats', method: RequestMethod.GET },
    { path: 'cats', method: RequestMethod.POST },
    'cats/(.*)',
  )
  .forRoutes(CatsController);

HINT Phương thức exclude() hỗ trợ các tham số ký tự đại diện bằng cách sử dụng gói path-to-regexp.

Với ví dụ trên, LoggerMiddleware sẽ được liên kết với tất cả route được xác định bên trong CatsController ngoại trừ mà 3 tham số được truyền cho phương thức exclude().

Functional middleware

Lớp LoggerMiddleware mà chúng tôi đang sử dụng khá đơn giản. Nó không có thành viên, không có phương thức bổ sung và không có phụ thuộc. Tại sao chúng ta không thể định nghĩa nó trong một hàm đơn giản thay vì một lớp? Trong thực tế, chúng tôi có thể. Loại middleware này được gọi là hàm middleware. Hãy chuyển đổi middleware của logger middleware từ dựa trên lớp thành hàm middleware để minh họa sự khác biệt:

logger.middleware.ts

import { Request, Response, NextFunction } from 'express';

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

Và sử dụng nó bên trong AppModule:

app.module.ts

consumer
  .apply(logger)
  .forRoutes(CatsController);

Multiple middleware

Như đã đề cập ở trên, để liên kết nhiều middleware được thực thi tuần tự, chỉ cần cung cấp một danh sách được phân tách bằng dấu phẩy bên trong phương thức apply():

consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);

Global middleware

Nếu chúng tôi muốn liên kết middleware với mọi route đã đăng ký cùng một lúc, chúng tôi có thể sử dụng phương thức use() được cung cấp bởi thực thể INestApplication:

const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(3000);

HINT Không thể truy cập DI container trong một global middleware. Bạn có thể sử dụng hàm middleware  thay vì sử dụng. app.use(). Ngoài ra, bạn có thể sử dụng lớp middleware và sử dụng nó với .forRoutes('*') trong AppModule (hoặc bất kỳ module nào khác).

One Comment on “Tổng quan – NestJS

  1. Nếu một interceptor handle() được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi. => Nếu một interceptor handle() “”không”” được gọi ở bất kỳ đâu trên đường đi, phương thức create() sẽ không được thực thi.

Leave a Reply

%d bloggers like this: