+5

Middleware trong NestJS

Giới thiệu

Rất vui được gặp lại các bạn trong 1 bài viết tiếp tục chủ đề NestJs 👋.

Trong các ứng dụng web hiện đại, việc quản lý và xử lý các yêu cầu HTTP là rất quan trọng. Middleware trong NestJS là một công cụ mạnh mẽ giúp bạn thực hiện các tác vụ trước khi yêu cầu được xử lý bởi controller. Middleware cho phép bạn thực hiện các nhiệm vụ như xác thực, ghi log, xử lý lỗi, hoặc thay đổi đối tượng yêu cầu, từ đó giúp bạn quản lý và kiểm soát các yêu cầu một cách hiệu quả.

Cấu trúc middleware

image.png

Middleware trong NestJS có thể được định nghĩa dưới dạng class hoặc function. Dưới đây là cách bạn có thể tạo và sử dụng middleware trong ứng dụng NestJS của mình.

Middleware dưới dạng class

Middleware dưới dạng lớp sử dụng interface NestMiddleware và cần implement phương thức use. Đây là cách phổ biến để định nghĩa middleware trong NestJS.

1. Tạo class Middleware

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

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Incoming Request: ${req.method} ${req.url}`);
    next();
  }
}

Trong ví dụ này, LoggingMiddleware ghi log thông tin về HTTP request đến console. Phương thức use nhận đối tượng yêu cầu (req), đối tượng phản hồi (res), và hàm tiếp theo (next). Sau khi ghi log, nó gọi next() để chuyển yêu cầu đến middleware tiếp theo hoặc controller.

2. Đăng ký Middleware trong Module

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { LoggingMiddleware } from './logging.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes('*'); // Áp dụng cho tất cả các route
  }
}

LoggingMiddleware được đăng ký trong AppModule và áp dụng cho tất cả các route (*). Điều này có nghĩa là tất cả các HTTP request sẽ đi qua middleware này trước khi được xử lý bởi controller.

Middleware dưới dạng hàm

Ngoài việc định nghĩa middleware dưới dạng class, bạn cũng có thể định nghĩa nó dưới dạng các function. Đây là cách đơn giản và nhẹ nhàng hơn, phù hợp cho các tác vụ cơ bản.

1. Tạo Middleware hàm

// request-time.middleware.ts
import { Request, Response, NextFunction } from 'express';

export function requestTimeMiddleware(req: Request, res: Response, next: NextFunction) {
  console.log(`Request Time: ${Date.now()}`);
  next();
}

Trong ví dụ này, requestTimeMiddleware ghi log thời gian của yêu cầu đến console. Sau khi ghi log, hàm next() được gọi để tiếp tục xử lý yêu cầu.

2. Đăng ký Middleware trong Module

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { requestTimeMiddleware } from './request-time.middleware';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(requestTimeMiddleware)
      .forRoutes('*'); // Áp dụng cho tất cả các route
  }
}

Tương tự như ví dụ với lớp, middleware function cũng được đăng ký trong AppModule và áp dụng cho tất cả các route.

Luồng Xử lý Middleware

Để hiểu rõ hơn cách middleware hoạt động trong ứng dụng NestJS, hãy xem xét một luồng xử lý điển hình của một HTTP request.

Sơ đồ Luồng Middleware

image.png

[Request] -> [Middleware 1] -> [Middleware 2] -> ... -> [Middleware N] -> [Controller] -> [Response]

Ví dụ Luồng Xử lý

  1. Nhận request: Khi một yêu cầu HTTP đến ứng dụng, nó được gửi đến middleware đầu tiên trong chuỗi.
  2. Xử lý qua Middleware: Mỗi middleware trong chuỗi thực hiện tác vụ của mình (như ghi log, xác thực) và sau đó gọi hàm next().
  3. Tiếp tục đến Controller: Sau khi tất cả các middleware đã thực hiện xong, yêu cầu sẽ được chuyển đến controller để xử lý.
  4. Phản hồi về Client: Controller xử lý yêu cầu và gửi phản hồi về client.

Ví dụ

Giả sử bạn có một ứng dụng với ba middleware và một controller.

1. Middleware 1 - Logging

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

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Logging Request: ${req.method} ${req.url}`);
    next();
  }
}

2. Middleware 2 - Authentication

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

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // Giả sử kiểm tra xác thực ở đây
    if (req.headers.authorization) {
      next();
    } else {
      res.status(401).send('Unauthorized');
    }
  }
}

3. Middleware 3 - Request Timing

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

@Injectable()
export class TimingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    req['start'] = Date.now();
    next();
  }
}

4. Controller

// cats.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  getAllCats() {
    return 'List of all cats';
  }
}

5. Đăng ký Middleware trong Module

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { LoggingMiddleware } from './logging.middleware';
import { AuthMiddleware } from './auth.middleware';
import { TimingMiddleware } from './timing.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware, AuthMiddleware, TimingMiddleware)
      .forRoutes('cats'); // Áp dụng cho route /cats
  }
}

Trong ví dụ này, khi yêu cầu đến route '/cats', nó sẽ đi qua các middleware sau theo thứ tự: LoggingMiddleware, AuthMiddleware, và TimingMiddleware. Sau khi tất cả middleware đã xử lý, yêu cầu sẽ được chuyển đến controller để lấy dữ liệu và gửi phản hồi về client.

Các Tùy Chọn Cấu Hình Middleware

NestJS cung cấp nhiều tùy chọn để cấu hình middleware sao cho phù hợp với nhu cầu của ứng dụng của bạn.

Áp dụng Middleware cho các Route Cụ Thể

consumer
  .apply(LoggingMiddleware)
  .forRoutes('cats'); // Áp dụng cho route /cats

Áp dụng Middleware cho các Method HTTP Cụ Thể

consumer
  .apply(LoggingMiddleware)
  .forRoutes({ path: 'cats', method: RequestMethod.GET }); // Áp dụng cho phương thức GET của route /cats

Áp dụng Middleware cho Nhiều Route

consumer
  .apply(LoggingMiddleware)
  .forRoutes({ path: 'cats', method: RequestMethod.ALL }, { path: 'dogs', method: RequestMethod.ALL }); // Áp dụng cho cả hai route

Kết hợp Middleware với Guards và Interceptors

Middleware có thể được kết hợp với guards và interceptors để tạo ra một hệ thống xử lý yêu cầu linh hoạt và mạnh mẽ hơn.

Guards

Guards trong NestJS được sử dụng để thực hiện các kiểm tra xác thực và phân quyền. Chúng quyết định xem yêu cầu có được tiếp tục xử lý hay không dựa trên các điều kiện xác định.

Interceptors

Interceptors cho phép bạn can thiệp vào quy trình xử lý yêu cầu và phản hồi, thực hiện các hành động trước và sau khi xử lý yêu cầu. Chúng có thể được sử dụng để ghi log, xử lý dữ liệu, hoặc thay đổi phản hồi trước khi trả về cho client.

Khi kết hợp, nó sẽ đi qua các thành phần sau:

  1. Middleware: Được thực thi theo thứ tự đã đăng ký, trước khi yêu cầu đến Guards.
  2. Guards: Xác định liệu yêu cầu có được tiếp tục hay không, sau khi các middleware đã hoàn tất.
  3. Interceptors: Thực hiện các tác vụ trước và sau khi xử lý yêu cầu trong controller.

Dưới đây là cách bạn có thể kết hợp chúng:

1. Tạo Middleware

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

@Injectable()
export class LoggingMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request Received: ${req.method} ${req.url}`);
    next();
  }
}

2. Tạo Guard

// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    // Kiểm tra xác thực (ví dụ: kiểm tra header Authorization)
    return !!request.headers.authorization;
  }
}

3. Tạo Interception

// logging.interceptor.ts
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> {
    const now = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`Request processed in ${Date.now() - now}ms`))
    );
  }
}

4. Đăng ký Middleware, Guards và Interceptors trong Module

// app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';
import { LoggingMiddleware } from './logging.middleware';
import { AuthGuard } from './auth.guard';
import { LoggingInterceptor } from './logging.interceptor';
import { APP_INTERCEPTOR, APP_GUARD } from '@nestjs/core';

@Module({
  controllers: [CatsController],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: LoggingInterceptor,
    },
    {
      provide: APP_GUARD,
      useClass: AuthGuard,
    },
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggingMiddleware)
      .forRoutes('*'); // Áp dụng cho tất cả các route
  }
}

Giải Thích Middleware (LoggingMiddleware): Được thực thi trước khi yêu cầu đến GuardsInterceptors. Nó ghi log thông tin về request đến console.

Guard (AuthGuard): Kiểm tra xác thực. Nếu yêu cầu không có header Authorization, Guard sẽ trả về false và yêu cầu sẽ bị từ chối trước khi đến controller.

Interceptor (LoggingInterceptor): Được thực thi sau khi yêu cầu đã qua Guardscontroller. Nó ghi log thời gian xử lý yêu cầu, giúp theo dõi hiệu suất của các yêu cầu.

Kết luận

Middleware trong NestJS là một công cụ rất hữu ích để kiểm soát và quản lý các yêu cầu HTTP trong ứng dụng của bạn. Bằng cách sử dụng middleware, bạn có thể thực hiện các tác vụ như ghi log, xác thực, và xử lý lỗi một cách hiệu quả trước khi yêu cầu được chuyển đến controller. Middleware có thể được kết hợp với guardsinterceptors để tạo ra một hệ thống xử lý yêu cầu linh hoạt và mạnh mẽ, giúp bạn xây dựng các ứng dụng web mạnh mẽ, dễ bảo trì, và dễ mở rộng.

Việc nắm vững cách sử dụng middleware và cách cấu hình chúng sao cho phù hợp với nhu cầu của ứng dụng là rất quan trọng trong việc phát triển các ứng dụng web với NestJS.

Cảm ơn các bạn đã theo dõi bài viết của mình. Hẹn gặp lại ở các bài viết sau nhé !


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí