Building Powerful APIs with NestJS Controllers
Learn to Handle API Requests Efficiently Using NestJS Controllers
NestJS is a progressive Node.js framework that allows you to build efficient, reliable, and scalable server-side applications. One of the key components of a NestJS application is the controller, which is responsible for handling incoming requests, processing them, and returning appropriate responses. In this blog post, we'll dive deep into the world of NestJS controllers, exploring their structure, functionalities, and how to make the most out of them.
What Are Controllers?
Controllers in NestJS are classes that handle incoming HTTP requests and return responses to the client. They act as an intermediary between the client and the application’s business logic, processing requests, invoking services, and sending the appropriate responses. Each controller is responsible for a specific route or group of routes, making it easier to manage and organize the application’s behavior.
Key Responsibilities of Controllers
Routing: Controllers define the routes that the application responds to, mapping HTTP methods to specific controller actions.
Handling Requests: They process incoming requests, extract data (such as parameters and body), and perform necessary validations.
Response Management: Controllers prepare and send responses back to the client, often returning JSON data.
Invoking Services: Controllers delegate complex logic and data handling to services, keeping the controller's responsibilities focused and maintainable.
Creating a Basic Controller
1. Setting Up a NestJS Project
Before creating a controller, ensure you have a NestJS project set up. If you haven’t done this yet, follow these steps:
npm install -g @nestjs/cli
nest new my-nestjs-app
cd my-nestjs-app
2. Generating a Controller
You can create a controller using the NestJS CLI. For example, to create a cats
controller, run:
nest generate controller cats
This command will generate a new controller file, typically located in the src/cats
directory, named cats.controller.ts
.
import { Controller, Get } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
}
In this example:
@Controller('cats')
: This decorator marks the class as a controller and sets the base route for all actions within this controller to/cats
.@Get()
: This decorator indicates that thefindAll
method will handle GET requests to the/cats
endpoint.
Using Controller Decorators
NestJS provides a variety of decorators to simplify the creation of RESTful APIs. Here are some of the most commonly used decorators:
Route Decorators
GET: Handles GET requests.
@Get() findAll() { return 'This action returns all cats'; }
POST: Handles POST requests.
@Post() create() { return 'This action adds a new cat'; }
PUT: Handles PUT requests.
@Put(':id') update(@Param('id') id: string) { return `This action updates cat with id ${id}`; }
DELETE: Handles DELETE requests.
@Delete(':id') remove(@Param('id') id: string) { return `This action removes cat with id ${id}`; }
Parameter Decorators
@Param(): Extracts route parameters from the request.
@Get(':id') findOne(@Param('id') id: string) { return `This action returns cat with id ${id}`; }
@Body(): Extracts the body of the request.
@Post() create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; }
@Query(): Extracts query parameters from the request.
@Get() findAll(@Query('limit') limit: number) { return `This action returns all cats with a limit of ${limit}`; }
Best Practices for Using Controllers
1. Keep Controllers Thin
Controllers should primarily handle request and response handling. Business logic should be delegated to services. This helps maintain clean and testable code.
2. Use DTOs (Data Transfer Objects)
DTOs help validate and structure the data coming into your controller. They ensure that the data adheres to a defined schema, making your application more robust.
Install the necessary packages:
npm install class-validator class-transformer
Example DTO:
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
readonly name: string;
@IsInt()
readonly age: number;
@IsString()
readonly breed: string;
}
Using DTOs in a controller:
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { CreateCatDto } from './create-cat.dto';
@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action returns all cats';
}
@Get(':id')
findOne(@Param('id') id: string): string {
return `This action returns cat with id ${id}`;
}
@Post()
create(@Body() createCatDto: CreateCatDto): string {
return `This action adds a new cat: ${createCatDto.name}`;
}
@Put(':id')
update(@Param('id') id: string, @Body() updateCatDto: CreateCatDto): string {
return `This action updates cat with id ${id}`;
}
@Delete(':id')
remove(@Param('id') id: string): string {
return `This action removes cat with id ${id}`;
}
}
3. Handle Errors Gracefully
Ensure your controllers handle errors gracefully. You can use exception filters or the built-in exception handling provided by NestJS to manage errors effectively.
import { HttpException, HttpStatus } from '@nestjs/common';
@Get(':id')
findOne(@Param('id') id: string) {
const cat = this.catsService.findOne(id);
if (!cat) {
throw new HttpException('Cat not found', HttpStatus.NOT_FOUND);
}
return cat;
}
4. Use Dependency Injection
Controllers should leverage NestJS’s dependency injection to access services and repositories. This promotes a clean separation of concerns and enhances testability.
import { Controller, Get } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
}
5. Document Your APIs
Use tools like Swagger to document your API endpoints. NestJS makes it easy to integrate Swagger, allowing you to generate documentation automatically based on your controller decorators.
To enable Swagger, you can add the following code to your main.ts
file:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
Conclusion
With the power of NestJS, you can build robust server-side applications that can grow with your needs while maintaining clarity and organization in your codebase. Happy coding!