Skip to main content

Execution Context trong NestJS

Execution Context là một object chứa thông tin chi tiết về request hiện tại đang được xử lý. Nó được sử dụng trong Guards, Interceptors, Exception Filters, và Pipes để truy cập các thông tin như HTTP request, response, handler method, class, arguments, v.v.

Khái Niệm Execution Context

Execution Context cung cấp truy cập vào:

  • HTTP Request/Response objects (Express)
  • Handler class được gọi
  • Handler method đang thực thi
  • Arguments của method
  • Class metadata (custom decorators)
  • Execution type (HTTP, WebSocket, RPC)
import { ExecutionContext } from '@nestjs/common';

@Injectable()
export class MyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Truy cập information từ execution context
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const handler = context.getHandler();
const classRef = context.getClass();

console.log(`Executing: ${classRef.name}.${handler.name}`);
console.log(`Method: ${request.method}`);
console.log(`URL: ${request.url}`);

return true;
}
}

ExecutionContext API

switchToHttp()

@Injectable()
export class HttpContextGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const httpContext = context.switchToHttp();

const request = httpContext.getRequest<Request>();
const response = httpContext.getResponse<Response>();
const next = httpContext.getNext<Function>();

console.log(`Method: ${request.method}`);
console.log(`URL: ${request.url}`);
console.log(`Headers: ${JSON.stringify(request.headers)}`);

return true;
}
}

switchToWs()

@Injectable()
export class WsContextGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const wsContext = context.switchToWs();

const client = wsContext.getClient();
const data = wsContext.getData();

console.log(`Client connected: ${client.id}`);
console.log(`Data: ${JSON.stringify(data)}`);

return true;
}
}

switchToRpc()

@Injectable()
export class RpcContextGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const rpcContext = context.switchToRpc();

const data = rpcContext.getData();
const context_arg = rpcContext.getContext();

console.log(`RPC Data: ${JSON.stringify(data)}`);
console.log(`RPC Context: ${JSON.stringify(context_arg)}`);

return true;
}
}

getHandler() & getClass()

@Injectable()
export class MetadataGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
// Lấy handler method và class
const handler = context.getHandler();
const classRef = context.getClass();

const handlerName = handler.name; // Method name
const className = classRef.name; // Class name

console.log(`Calling: ${className}.${handlerName}`);

// Lấy custom metadata
const roles = this.reflector.get<string[]>(
'roles',
handler,
);

const isPublic = this.reflector.get<boolean>(
'public',
handler,
);

console.log(`Required roles: ${roles}`);
console.log(`Is public: ${isPublic}`);

return true;
}
}

getArgs() & getArgByIndex()

@Injectable()
export class ArgumentsGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Lấy tất cả arguments
const args = context.getArgs();
console.log(`Total arguments: ${args.length}`);

// Lấy argument cụ thể
const request = context.getArgByIndex(0);
const response = context.getArgByIndex(1);
const next = context.getArgByIndex(2);

console.log(`Request: ${request.method} ${request.url}`);

return true;
}
}

getType()

@Injectable()
export class TypeAwareGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const type = context.getType();

if (type === 'http') {
const request = context.switchToHttp().getRequest();
console.log(`HTTP Request: ${request.method} ${request.url}`);
} else if (type === 'ws') {
const client = context.switchToWs().getClient();
console.log(`WebSocket Client: ${client.id}`);
} else if (type === 'rpc') {
const data = context.switchToRpc().getData();
console.log(`RPC Data: ${JSON.stringify(data)}`);
}

return true;
}
}

Ví Dụ Execution Context Thực Tế

1. Request Logging with Context

@Injectable()
export class RequestLoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('HTTP');

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<Request>();
const { method, url, headers, ip } = request;
const userAgent = headers['user-agent'];
const startTime = Date.now();

// Get handler info
const handler = context.getHandler();
const className = context.getClass().name;
const handlerName = handler.name;

this.logger.log(
`→ [${className}:${handlerName}] ${method} ${url} from ${ip}`,
);

return next.handle().pipe(
tap((data) => {
const duration = Date.now() - startTime;
const response = context.switchToHttp().getResponse<Response>();

this.logger.log(
`← [${className}:${handlerName}] ${response.statusCode} ${duration}ms`,
);
}),
catchError((error) => {
const duration = Date.now() - startTime;
this.logger.error(
`✗ [${className}:${handlerName}] ERROR ${duration}ms: ${error.message}`,
);
throw error;
}),
);
}
}

2. Role-Based Authorization Guard

export const Roles = Reflector.createDecorator<string[]>();

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
// Lấy roles từ method decorator
const requiredRoles = this.reflector.get(Roles, context.getHandler());

if (!requiredRoles) {
// Nếu không có roles required, cho phép truy cập
return true;
}

// Lấy user từ request
const request = context.switchToHttp().getRequest();
const user = (request as any).user;

if (!user) {
throw new ForbiddenException('User not found');
}

// Check nếu user có required roles
const hasRole = requiredRoles.some((role) => user.roles?.includes(role));

if (!hasRole) {
throw new ForbiddenException(
`User must have one of these roles: ${requiredRoles.join(', ')}`,
);
}

return true;
}
}

// Sử dụng
@Controller('admin')
export class AdminController {
@Get('dashboard')
@Roles(['admin'])
getDashboard() {
return { data: 'admin dashboard' };
}

@Post('users')
@Roles(['admin', 'moderator'])
createUser() {
return { message: 'user created' };
}
}

3. Custom Decorator with Execution Context

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

// Lấy user từ request
export const CurrentUser = createParamDecorator(
(data: string | undefined, context: ExecutionContext) => {
const request = context.switchToHttp().getRequest();
const user = (request as any).user;

if (!user) {
throw new UnauthorizedException('User not found');
}

return data ? user[data] : user;
},
);

// Sử dụng
@Controller('profile')
export class ProfileController {
@Get()
getProfile(@CurrentUser() user: any) {
return { user };
}

@Post('update')
updateProfile(
@CurrentUser('id') userId: string,
@Body() updateDto: UpdateProfileDto,
) {
return { userId, ...updateDto };
}
}

4. Metadata Extraction Guard

export const Permissions = Reflector.createDecorator<string[]>();
export const Public = Reflector.createDecorator<void>();
export const Throttle = Reflector.createDecorator<{ limit: number; ttl: number }>();

@Injectable()
export class MetadataExtractionGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
// Lấy metadata từ handler
const isPublic = this.reflector.getAllAndOverride(Public, [
context.getHandler(),
context.getClass(),
]);

if (isPublic) {
return true; // Public route, skip authorization
}

// Lấy permissions
const requiredPermissions = this.reflector.get(
Permissions,
context.getHandler(),
);

// Lấy throttle config
const throttleConfig = this.reflector.get(
Throttle,
context.getHandler(),
);

const request = context.switchToHttp().getRequest();
const user = (request as any).user;

console.log(`Permissions required: ${requiredPermissions}`);
console.log(`Throttle config: ${JSON.stringify(throttleConfig)}`);

return true;
}
}

// Sử dụng
@Controller('api')
export class ApiController {
@Get('public')
@Public()
getPublic() {
return { data: 'public' };
}

@Post('users')
@Permissions(['users:create'])
@Throttle({ limit: 5, ttl: 60 })
createUser() {
return { message: 'user created' };
}
}

5. Dynamic Handler Execution

@Injectable()
export class DynamicExecutionInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const handler = context.getHandler();
const classRef = context.getClass();
const request = context.switchToHttp().getRequest();

// Lấy metadata về execution
const handlerName = handler.name;
const className = classRef.name;
const httpMethod = request.method;

console.log(`Executing: ${className}#${handlerName} [${httpMethod}]`);

// Có thể dynamically modify handler behavior dựa trên metadata
const customMeta = Reflect.getMetadata('custom', handler);

if (customMeta?.requiresAuth) {
const user = (request as any).user;
if (!user) {
throw new UnauthorizedException();
}
}

return next.handle();
}
}

6. Request Validation with Context

@Injectable()
export class RequestValidationGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const handler = context.getHandler();

// Validate based on handler metadata
const allowedMethods = this.reflector.get<string[]>(
'allowedMethods',
handler,
);

if (allowedMethods && !allowedMethods.includes(request.method)) {
throw new BadRequestException(
`Method ${request.method} not allowed`,
);
}

// Validate headers
const requiredHeaders = this.reflector.get<string[]>(
'requiredHeaders',
handler,
);

if (requiredHeaders) {
for (const header of requiredHeaders) {
if (!request.headers[header.toLowerCase()]) {
throw new BadRequestException(
`Missing required header: ${header}`,
);
}
}
}

return true;
}
}

// Decorators
export const AllowedMethods = (methods: string[]) =>
SetMetadata('allowedMethods', methods);

export const RequiredHeaders = (headers: string[]) =>
SetMetadata('requiredHeaders', headers);

// Sử dụng
@Controller('api')
export class ApiController {
@Post('data')
@AllowedMethods(['POST', 'PUT'])
@RequiredHeaders(['Content-Type', 'Authorization'])
processData(@Body() data: any) {
return { success: true };
}
}

7. Performance Monitoring with Context

@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
private readonly logger = new Logger('Performance');

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const startTime = performance.now();
const startMemory = process.memoryUsage();

const request = context.switchToHttp().getRequest();
const handler = context.getHandler();
const classRef = context.getClass();

return next.handle().pipe(
tap(() => {
const duration = performance.now() - startTime;
const endMemory = process.memoryUsage();

const memoryDelta = {
heapUsed:
Math.round((endMemory.heapUsed - startMemory.heapUsed) / 1024) +
'KB',
heapTotal:
Math.round((endMemory.heapTotal - startMemory.heapTotal) / 1024) +
'KB',
};

const metrics = {
handler: `${classRef.name}#${handler.name}`,
method: request.method,
path: request.path,
duration: `${duration.toFixed(2)}ms`,
memory: memoryDelta,
};

if (duration > 1000) {
this.logger.warn(`Slow handler: ${JSON.stringify(metrics)}`);
} else {
this.logger.debug(`Handler executed: ${JSON.stringify(metrics)}`);
}
}),
catchError((error) => {
const duration = performance.now() - startTime;
this.logger.error(
`Error in ${classRef.name}#${handler.name}: ${error.message} (${duration.toFixed(2)}ms)`,
);
throw error;
}),
);
}
}

8. Conditional Response Transformation

@Injectable()
export class ConditionalTransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const handler = context.getHandler();
const shouldTransform = Reflect.getMetadata('transform', handler);

if (!shouldTransform) {
return next.handle();
}

return next.handle().pipe(
map((data) => {
// Transform response based on metadata
const transformType = Reflect.getMetadata('transformType', handler);

switch (transformType) {
case 'pagination':
return {
data,
page: 1,
limit: 10,
total: data.length,
};
case 'envelope':
return {
success: true,
timestamp: new Date(),
data,
};
default:
return data;
}
}),
);
}
}

// Decorators
export const Transform = (type: string) => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
Reflect.defineMetadata('transform', true, descriptor.value);
Reflect.defineMetadata('transformType', type, descriptor.value);
return descriptor;
};
};

// Sử dụng
@Controller('api')
export class ApiController {
@Get('users')
@Transform('pagination')
getUsers() {
return ['Alice', 'Bob', 'Charlie'];
}

@Get('data')
@Transform('envelope')
getData() {
return { key: 'value' };
}
}

9. Multi-Type Context Handling

@Injectable()
export class MultiContextGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const contextType = context.getType();

if (contextType === 'http') {
return this.handleHttp(context);
} else if (contextType === 'ws') {
return this.handleWs(context);
} else if (contextType === 'rpc') {
return this.handleRpc(context);
}

return false;
}

private handleHttp(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = (request as any).user;

console.log(`HTTP: User ${user?.id} accessing ${request.path}`);
return !!user;
}

private handleWs(context: ExecutionContext): boolean {
const client = context.switchToWs().getClient();
const data = context.switchToWs().getData();

console.log(`WebSocket: Client ${client.id} sent data: ${JSON.stringify(data)}`);
return true;
}

private handleRpc(context: ExecutionContext): boolean {
const data = context.switchToRpc().getData();

console.log(`RPC: Received data: ${JSON.stringify(data)}`);
return true;
}
}

10. Audit Logging with Context

@Injectable()
export class AuditLoggingInterceptor implements NestInterceptor {
constructor(private auditService: AuditService) {}

intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const handler = context.getHandler();
const classRef = context.getClass();

const auditData = {
action: `${classRef.name}#${handler.name}`,
method: request.method,
path: request.path,
userId: (request as any).user?.id,
timestamp: new Date(),
ip: request.ip,
userAgent: request.headers['user-agent'],
};

return next.handle().pipe(
tap(() => {
// Log successful action
this.auditService.log({
...auditData,
status: 'success',
});
}),
catchError((error) => {
// Log failed action
this.auditService.log({
...auditData,
status: 'error',
error: error.message,
});
throw error;
}),
);
}
}

@Injectable()
export class AuditService {
log(auditData: any) {
console.log(`[AUDIT] ${JSON.stringify(auditData)}`);
// Save to database, file, or external service
}
}

Execution Context in Different Contexts

HTTP Context

const httpContext = context.switchToHttp();
const request = httpContext.getRequest<Request>(); // Express Request
const response = httpContext.getResponse<Response>(); // Express Response
const next = httpContext.getNext<Function>(); // Express next()

WebSocket Context

const wsContext = context.switchToWs();
const client = wsContext.getClient(); // WebSocket client
const data = wsContext.getData(); // Data from message

RPC Context

const rpcContext = context.switchToRpc();
const data = rpcContext.getData(); // RPC data
const context_arg = rpcContext.getContext(); // RPC context

Best Practices

1. Check Execution Type Before Switching

// ❌ Sai - Assume HTTP context
const request = context.switchToHttp().getRequest();

// ✅ Đúng - Check type first
if (context.getType() === 'http') {
const request = context.switchToHttp().getRequest();
}

2. Use Type Guards

// ✅ Đúng - Type-safe context switching
const isHttpContext = (context: ExecutionContext): boolean => {
return context.getType() === 'http';
};

if (isHttpContext(context)) {
const request = context.switchToHttp().getRequest<Request>();
// Type-safe operations
}

3. Cache Context Information

// ✅ Đúng - Cache expensive operations
@Injectable()
export class OptimizedGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// Cache expensive lookups
const handler = context.getHandler();
const metadata = Reflect.getMetadata('key', handler);

// Reuse cached values
return !!metadata;
}
}

4. Use Reflector for Metadata

// ✅ Đúng - Use Reflector API
const roles = this.reflector.get<string[]>('roles', context.getHandler());

// ❌ Sai - Direct metadata access
const roles = Reflect.getMetadata('roles', context.getHandler());

5. Error Handling with Context

// ✅ Đúng - Provide context in errors
if (!user) {
const request = context.switchToHttp().getRequest();
throw new UnauthorizedException(
`Unauthorized access to ${request.path}`,
);
}

Complete Example

// decorators/require-roles.decorator.ts
export const RequireRoles = Reflector.createDecorator<string[]>();
export const RequirePermissions = Reflector.createDecorator<string[]>();

// guards/authorization.guard.ts
@Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const handler = context.getHandler();
const classRef = context.getClass();
const request = context.switchToHttp().getRequest();

// Get required roles and permissions
const requiredRoles = this.reflector.get(
RequireRoles,
handler,
) || [];

const requiredPermissions = this.reflector.get(
RequirePermissions,
handler,
) || [];

if (requiredRoles.length === 0 && requiredPermissions.length === 0) {
return true; // No authorization required
}

const user = (request as any).user;

if (!user) {
throw new UnauthorizedException('User not authenticated');
}

// Check roles
if (requiredRoles.length > 0) {
const hasRole = requiredRoles.some((role) =>
user.roles?.includes(role),
);

if (!hasRole) {
throw new ForbiddenException(
`Required roles: ${requiredRoles.join(', ')}`,
);
}
}

// Check permissions
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.every((permission) =>
user.permissions?.includes(permission),
);

if (!hasPermission) {
throw new ForbiddenException(
`Required permissions: ${requiredPermissions.join(', ')}`,
);
}
}

return true;
}
}

// controllers/users.controller.ts
@Controller('users')
@UseGuards(AuthorizationGuard)
export class UsersController {
@Get()
@RequireRoles(['admin', 'moderator'])
findAll() {
return [];
}

@Post()
@RequireRoles(['admin'])
@RequirePermissions(['users:create'])
create(@Body() createUserDto: CreateUserDto) {
return { message: 'User created' };
}

@Delete(':id')
@RequireRoles(['admin'])
@RequirePermissions(['users:delete'])
remove(@Param('id') id: string) {
return { message: 'User deleted' };
}
}

// interceptors/audit.interceptor.ts
@Injectable()
export class AuditInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const handler = context.getHandler();
const classRef = context.getClass();

const auditData = {
action: `${classRef.name}#${handler.name}`,
method: request.method,
path: request.path,
userId: (request as any).user?.id,
timestamp: new Date().toISOString(),
};

console.log(`[AUDIT] ${JSON.stringify(auditData)}`);

return next.handle();
}
}

Kết Luận

Execution Context là công cụ mạnh mẽ để:

  • Truy cập thông tin về request hiện tại
  • Extract metadata từ handlers
  • Implement authorization logic
  • Log và audit operations
  • Handle multiple execution types

Sử dụng Execution Context đúng cách giúp bạn:

  • Xây dựng flexible guards và interceptors
  • Implement fine-grained access control
  • Tạo powerful audit logging
  • Handle complex authorization scenarios
  • Support multiple execution contexts (HTTP, WebSocket, RPC)

Execution Context là nền tảng của nhiều advanced NestJS patterns và features!