Chapter 3: Controllers & Routing
Controllers are the entry point for handling client requests in NestJS. Think of them as the “reception desk” of your application—they receive requests, ask the right service for help, and send back a response. This chapter explores routing, request/response handling, decorators, middleware, guards, interceptors, error handling, and best practices for building robust APIs.
3.1 What is a Controller?
A controller is responsible for receiving incoming requests, delegating work to services, and returning responses. Controllers define the routes and HTTP methods for your API.Controller Responsibilities
Controllers handle HTTP-specific concerns:- Route definition: Map URLs to handler methods
- Request extraction: Get data from params, query, body, headers
- Response formatting: Return data in the correct format
- Error handling: Throw appropriate HTTP exceptions
- Validation: Ensure incoming data is valid (via DTOs and pipes)
- Business logic (delegate to services)
- Database access (delegate to repositories/services)
- Complex data transformation (delegate to services)
- Authentication logic (use guards)
Imagine a restaurant: the controller is the waiter (takes orders, serves food), the service is the chef (prepares the meal), and the client is the customer.
Basic Controller Structure
@Controller('users')- Defines the base route for all methods- Constructor injection - Services are injected via constructor
- Route decorators -
@Get(),@Post(), etc. define HTTP methods - Handler methods - Process requests and return responses
3.2 Routing in NestJS
Routes are defined using decorators like@Get, @Post, @Put, @Delete, @Patch, etc. The path can include parameters, query strings, and wildcards.
HTTP Method Decorators
NestJS provides decorators for all HTTP methods:Route Parameters
Extract dynamic segments from the URL:Query Parameters
Extract data from the query string:Request Body
Extract data from the request body:Headers
Extract data from request headers:Request Object
Access the full request object when needed:Response Object
Control the response directly:@Res() bypasses NestJS response handling. Use with caution, or return data and let NestJS handle the response.
Status Codes
Set custom HTTP status codes:Route Wildcards
Use wildcards for flexible routing:3.3 Request Lifecycle
Understanding the request lifecycle helps you know where to place your logic and how different components interact.Complete Request Lifecycle
- Incoming Request - HTTP request arrives at the server
- Middleware - Global and route-specific middleware runs
- Guards - Authentication and authorization checks
- Interceptors (Before) - Pre-processing (logging, transformation)
- Pipes - Validation and transformation of input data
- Controller - Route handler method executes
- Service - Business logic executes
- Interceptors (After) - Post-processing (response transformation)
- Exception Filters - Handle any exceptions
- Response - HTTP response sent to client
Execution Order Example
3.4 Validation & DTOs
Data Transfer Objects (DTOs) define the shape of data for requests and responses. Combined with validation, they ensure your API only accepts well-formed requests.Why Use DTOs?
- Type Safety: TypeScript knows the shape of your data
- Validation: Ensure data meets requirements
- Documentation: DTOs document your API contract
- Transformation: Can transform data automatically
- Security: Prevent invalid or malicious data
Basic DTO
DTO with Validation
Install validation packages:Common Validation Decorators
Global Validation Pipe
Apply validation globally inmain.ts:
Custom Validation
Create custom validators:3.5 Middleware
Middleware is executed before the route handler. Use it for logging, authentication, request transformation, CORS, and other cross-cutting concerns.What is Middleware?
Middleware functions have access to:- Request object
- Response object
- Next function (to pass control to next middleware)
- Execute code before/after the route handler
- Modify request/response objects
- End the request-response cycle
- Call the next middleware in the stack
Functional Middleware
Simple middleware as a function:Class-Based Middleware
More powerful, can inject dependencies:Middleware with Dependencies
Registering Middleware
Register in module usingconfigure method:
Route-Specific Middleware
Excluding Routes
Multiple Middleware
Global Middleware
Apply middleware globally inmain.ts:
3.6 Guards
Guards determine whether a request should be handled by the route. They run after middleware but before the route handler. Use them for authentication and authorization.What are Guards?
Guards have a single responsibility: determine if a request should proceed. They return:true- Request proceedsfalse- Request is denied (throws ForbiddenException)
Basic Guard
Using Guards
Controller-level:Execution Context
TheExecutionContext provides access to:
- Request object
- Response object
- Next function
- Handler (controller method)
- Class (controller class)
Role-Based Guard
Async Guards
Guards can be async:3.7 Interceptors
Interceptors can transform the result returned from a function, extend basic method behavior, or handle cross-cutting concerns like logging, caching, or response shaping.What are Interceptors?
Interceptors are similar to middleware but have access to:- Execution context
- Call handler (to invoke the route handler)
- Observable streams (for async operations)
- Execute code before/after method execution
- Transform the result
- Transform exceptions
- Extend method behavior
Basic Interceptor
Response Transformation Interceptor
Wrap responses in a consistent format:Error Transformation Interceptor
Transform errors into consistent format:Caching Interceptor
Cache responses:Timeout Interceptor
Add timeout to requests:Using Interceptors
Controller-level:3.8 Error Handling
NestJS provides built-in exception filters and HTTP exceptions for consistent error handling.Built-in HTTP Exceptions
Custom Exception Messages
Exception Filters
Create custom exception filters:3.9 Best Practices
Following best practices ensures your controllers are maintainable and scalable.Keep Controllers Thin
Controllers should only handle HTTP concerns:Use DTOs for All Input
Always validate input using DTOs:Organize Routes by Feature
Group related routes in feature modules:Use Appropriate HTTP Methods
Return Consistent Response Shapes
Use interceptors to format responses consistently:Handle Errors Gracefully
Use built-in exceptions:Document Your API
Use Swagger for API documentation:Use Pipes for Transformation
3.10 Summary
You’ve learned how to build robust, maintainable APIs using controllers and routing: Key Concepts:- Controllers: Handle HTTP requests and responses
- Routing: Define routes using decorators
- DTOs: Validate and transform input data
- Middleware: Cross-cutting concerns before route handlers
- Guards: Authentication and authorization
- Interceptors: Response transformation and logging
- Error Handling: Consistent error responses
- Keep controllers thin
- Use DTOs for validation
- Organize routes by feature
- Use appropriate HTTP methods
- Handle errors gracefully
- Document your API