
Written on 3rd September 2026 by Carter Phan.
Khi một HTTP request đi vào NestJS, NestJS không chạy thẳng vào controller.
Request đi qua một pipeline nhiều tầng, mỗi tầng có một trách nhiệm khác nhau:
Client
│
▼
Middleware
│
▼
Guards
│
▼
Interceptors ────────┐
│ │
▼ │
Pipes │
│ │
▼ │
Controller Handler │
│ │
▼ │
Service │
│ │
└──────────────────┘
│
▼
Interceptors (response)
│
▼
Exception Filters (nếu có exception)
│
▼
HTTP Response
Điểm quan trọng khi interview:
Middleware → Guards → Interceptors → Pipes → Controller → Service → Interceptor response → Response
Exception Filters là một nhánh xử lý exception, không phải một bước luôn chạy sau controller.
Middleware chạy trước Guards.
Request
│
▼
Middleware
│
▼
Guard
Middleware nhận:
(req, res, next)
Nó gần với middleware trong Express.
Những việc mang tính HTTP-level / request-level:
req
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method}${req.url}`);
next();
}
}
Middleware không biết route handler sẽ làm gì.
Nó chủ yếu quan tâm:
HTTP Request
↓
"Request này là gì?"
Sau middleware là Guards.
Middleware
│
▼
Guard
Guard quyết định:
Request này có được phép đi tiếp hay không?
Ví dụ:
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
return !!request.user;
}
}
Nếu:
canActivate() = true
│
▼
tiếp tục
Nếu:
canActivate() = false
│
▼
403 Forbidden
Authentication / Authorization:
Request
│
▼
AuthGuard
│
├── ❌ chưa login → reject
│
└── ✅ authenticated
│
▼
Controller
Ví dụ thực tế:
@UseGuards(JwtAuthGuard)
@Get('/orders')
getOrders() {}
Vì Guard có access đến:
ExecutionContext
@Roles('admin')
@Get('/users')
getUsers() {}
Guard có thể đọc metadata Roles.
=> Authorization phù hợp với Guard hơn Middleware.
Sau Guards là Interceptors.
Middleware
↓
Guard
↓
Interceptor
↓
Pipes
↓
Controller
Interceptor có một điểm rất quan trọng:
Interceptor bao quanh execution của handler.
Có thể hình dung:
Interceptor BEFORE
│
▼
Controller
│
▼
Interceptor AFTER
Ví dụ:
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
console.log('Before');
return next.handle().pipe(
tap(() => console.log('After')),
);
}
}
Output:
Before
Controller
After
Interceptors rất phù hợp cho cross-cutting concerns:
Request
│
▼
Interceptor
│
│ start timer
▼
Controller
│
▼
Service
│
▼
Interceptor
│
│ calculate duration
▼
Response
Pipes thường chạy trước khi controller handler được gọi, chủ yếu để:
@Get(':id')
findUser(
@Param('id', ParseIntPipe) id: number
) {}
Request:
GET /users/123
Ban đầu:
id = "123"
Pipe transform:
"123"
↓
123
Controller nhận:
id: number
Use case phổ biến nhất:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
}),
);
DTO:
class CreateUserDto {
@IsEmail()
email: string;
@IsString()
name: string;
}
Request:
{
"email": "invalid",
"name": 123
}
Pipeline:
HTTP Request
│
▼
ValidationPipe
│
├── ❌ invalid
│ ↓
│ BadRequestException
│
└── ✅ valid
│
▼
Controller
Một cách nhớ đơn giản:
Middleware
→ xử lý HTTP request nói chung
Pipe
→ xử lý input của route handler
Sau khi request vượt qua Guards, Interceptors và Pipes:
Request
↓
Middleware
↓
Guard
↓
Interceptor
↓
Pipe
↓
Controller
Controller chỉ nên chịu trách nhiệm:
HTTP
↓
Controller
↓
Service
Ví dụ:
@Post()
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
Controller không nên chứa business logic phức tạp.
Service không phải một bước đặc biệt trong NestJS request lifecycle.
Nó đơn giản là nơi Controller gọi business logic:
Controller
│
▼
Service
│
├── Repository
├── Database
├── Redis
└── External API
Ví dụ:
async create(dto: CreateUserDto) {
const user = await this.repository.create(dto);
await this.eventPublisher.publish(
new UserCreatedEvent(user.id),
);
return user;
}
Đây là phần rất dễ trả lời sai trong interview.
Exception Filter không phải lúc nào cũng chạy sau Controller.
Nó xử lý exception được throw trong quá trình request.
Ví dụ:
throw new NotFoundException();
Flow có thể trở thành:
Controller
│
▼
Service
│
X
│
▼
Exception
│
▼
Exception Filter
│
▼
HTTP Response
Ví dụ custom filter:
@Catch(HttpException)
export class HttpExceptionFilter
implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const response = host
.switchToHttp()
.getResponse();
response.status(exception.getStatus()).json({
message: exception.message,
});
}
}
Use case:
Một request bình thường:
REQUEST
│
▼
Middleware
│
▼
Guards
│
▼
Interceptor
┌─────┴─────┐
│ │
BEFORE │
│ │
▼ │
Pipes │
│ │
▼ │
Controller │
│ │
▼ │
Service │
│ │
└─────┬─────┘
│
▼
Interceptor
AFTER
│
▼
RESPONSE
Nếu exception xảy ra:
Request
↓
Middleware
↓
Guard
↓
Interceptor
↓
Pipe
↓
Controller
↓
Service
↓
Exception
↓
Exception Filter
↓
HTTP Response
Giả sử:
POST /orders
Authorization: Bearer xxx
Content-Type: application/json
Body:
{
"productId": 123,
"quantity": 2
}
1. Middleware
│
├── Generate requestId
└── Log request
│
▼
2. JwtAuthGuard
│
├── Verify JWT
└── Attach user
│
▼
3. LoggingInterceptor
│
└── Start timer
│
▼
4. ValidationPipe
│
├── Validate productId
└── Validate quantity
│
▼
5. OrderController
│
▼
6. OrderService
│
├── Check inventory
├── Create order
└── Publish OrderCreated event
│
▼
7. LoggingInterceptor
│
└── Record latency
│
▼
8. HTTP Response
Nếu DTO invalid:
ValidationPipe
│
X
│
▼
BadRequestException
│
▼
Exception Filter
│
▼
400 Bad Request
Nếu JWT invalid:
JwtAuthGuard
│
X
│
▼
UnauthorizedException
│
▼
Exception Filter
│
▼
401 Unauthorized
Các component có thể được register ở nhiều scope:
Global
↓
Controller
↓
Route
Ví dụ:
app.useGlobalPipes(...)
hoặc:
@UseGuards(AuthGuard)
@Controller('orders')
hoặc:
@UseInterceptors(CacheInterceptor)
@Get()
findAll() {}
Thông thường:
Global
↓
Controller
↓
Method
Middleware xử lý HTTP request trước khi NestJS xác định/đi vào authorization pipeline.
Guard quyết định request có được phép execute route handler hay không và có access tới ExecutionContext + route metadata.
Middleware:
Request → Middleware → next()
Interceptor:
Request
↓
Interceptor BEFORE
↓
Handler
↓
Interceptor AFTER
Interceptor có thể wrap handler execution, nên phù hợp cho logging, metrics, caching, response transformation.
Guard trước Pipe.
Middleware
↓
Guard
↓
Interceptor
↓
Pipe
↓
Controller
Không nên nói:
"Filter chạy sau Controller."
Chính xác hơn:
Exception Filters handle exceptions thrown during request processing and convert them into an HTTP response.
Middleware: “Do something at the HTTP request layer before Nest handles the route.”
Guard: “Decide whether this request is allowed to execute the route.”
Pipe: “Validate or transform the data going into the handler.”
Interceptor: “Wrap route execution to add behavior before and/or after it.”
Exception filter: “Turn exceptions into the appropriate error response.”