Building Powerful APIs with NestJS Controllers

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

  1. Routing: Controllers define the routes that the application responds to, mapping HTTP methods to specific controller actions.

  2. Handling Requests: They process incoming requests, extract data (such as parameters and body), and perform necessary validations.

  3. Response Management: Controllers prepare and send responses back to the client, often returning JSON data.

  4. 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 the findAll 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

  1. GET: Handles GET requests.

     @Get()
     findAll() {
       return 'This action returns all cats';
     }
    
  2. POST: Handles POST requests.

     @Post()
     create() {
       return 'This action adds a new cat';
     }
    
  3. PUT: Handles PUT requests.

     @Put(':id')
     update(@Param('id') id: string) {
       return `This action updates cat with id ${id}`;
     }
    
  4. DELETE: Handles DELETE requests.

     @Delete(':id')
     remove(@Param('id') id: string) {
       return `This action removes cat with id ${id}`;
     }
    

Parameter Decorators

  1. @Param(): Extracts route parameters from the request.

     @Get(':id')
     findOne(@Param('id') id: string) {
       return `This action returns cat with id ${id}`;
     }
    
  2. @Body(): Extracts the body of the request.

     @Post()
     create(@Body() createCatDto: CreateCatDto) {
       return 'This action adds a new cat';
     }
    
  3. @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
Controllers in NestJS play a crucial role in building RESTful APIs. They handle incoming requests, manage routing, and interact with services to provide a clean and efficient way to process and respond to client requests. By following best practices, using decorators effectively, and keeping controllers thin, you can create maintainable and scalable applications that are easy to understand and extend.

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!