Skip to content

Interceptor

Interceptor có quyền truy cập vào yêu cầu/phản hồi trước và sau khi bộ xử lý route thực thi. Chúng hữu ích cho việc ghi log, chuyển đổi phản hồi, bộ nhớ đệm và nhiều việc khác.

Interceptor cơ bản

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@galaxy-stack/orbit-core';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    const now = Date.now();
    const request = context.switchToHttp().getRequest();
    
    console.log(`[${request.method}] ${request.url} - Bắt đầu`);
    
    const result = await next.handle();
    
    console.log(`[${request.method}] ${request.url} - ${Date.now() - now}ms`);
    
    return result;
  }
}

Sử dụng Interceptor

Cấp độ Controller

typescript
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UserController {}

Cấp độ Phương thức

typescript
@Controller('users')
export class UserController {
  @Get()
  @UseInterceptors(CacheInterceptor)
  findAll() {
    return [];
  }
}

Interceptor toàn cục

typescript
const app = await BunFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

Chuyển đổi phản hồi

typescript
@Injectable()
export class TransformInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    const data = await next.handle();
    
    return {
      success: true,
      data,
      timestamp: new Date().toISOString(),
    };
  }
}

Interceptor bộ nhớ đệm

typescript
@Injectable()
export class CacheInterceptor implements NestInterceptor {
  private cache = new Map<string, { data: any; expiry: number }>();

  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    const request = context.switchToHttp().getRequest();
    const key = request.url;
    
    const cached = this.cache.get(key);
    if (cached && cached.expiry > Date.now()) {
      return cached.data;
    }
    
    const data = await next.handle();
    
    this.cache.set(key, {
      data,
      expiry: Date.now() + 60000, // 1 phút
    });
    
    return data;
  }
}

Interceptor thời gian chờ

typescript
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    const timeoutPromise = new Promise((_, reject) => {
      setTimeout(() => reject(new Error('Yêu cầu hết thời gian chờ')), 5000);
    });

    return Promise.race([
      next.handle(),
      timeoutPromise,
    ]);
  }
}

Interceptor ánh xạ lỗi

typescript
@Injectable()
export class ErrorMappingInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    try {
      return await next.handle();
    } catch (error) {
      if (error.code === 'P2025') {
        throw new NotFoundException('Tài nguyên không tìm thấy');
      }
      throw error;
    }
  }
}

Interceptor tuần tự hóa

typescript
@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
  async intercept(context: ExecutionContext, next: CallHandler): Promise<any> {
    const data = await next.handle();
    return this.removeNulls(data);
  }

  private removeNulls(obj: any): any {
    if (Array.isArray(obj)) {
      return obj.map(item => this.removeNulls(item));
    }
    if (obj && typeof obj === 'object') {
      return Object.fromEntries(
        Object.entries(obj)
          .filter(([_, v]) => v !== null)
          .map(([k, v]) => [k, this.removeNulls(v)])
      );
    }
    return obj;
  }
}

Thứ tự thực thi

  1. Interceptor toàn cục (trước)
  2. Interceptor controller (trước)
  3. Interceptor phương thức (trước)
  4. Bộ xử lý route
  5. Interceptor phương thức (sau)
  6. Interceptor controller (sau)
  7. Interceptor toàn cục (sau)

Released under the MIT License.