You are already familiar with the basics of NestJS.

It is time to take the next step. And that step is to create your first NestJS Controller.

Controllers are a key ingredient in learning the NestJS fundamentals. In this post, I will show you how to build interfaces using controllers in NestJS.

Here’s the high-level plan:

  • Create a simple NestJS controller
  • Get a first look at classes and decorators
  • Say hello to the Request and Response objects
  • Use the @Param() and @Body() decorators

1 – What is a Controller?

In the world of web development, the controller is a component that acts as the entry point for incoming HTTP requests.

Think of it as the bouncer checking IDs at the club’s entrance, making sure only authorized guests are allowed in.

? OK. It was a lame comparison! I take it back.

In a nutshell, the job of a controller is to handle incoming requests from clients and return appropriate responses to the clients.

However, each request can differ from the other one. It can have different parameters or use another HTTP operation.

This is where the routing mechanism comes into the picture. This mechanism controls which request should go to which controller.

Here’s an illustration that shows the role of a routing mechanism in a typical web application. Based on the incoming request, the routing mechanism directs the request to the appropriate controller based on the properties of the incoming request.

nestjs controllers
Controllers in NestJS

2 – NestJS Controller Demo

To get a better idea, let’s see a controller in action.

In the src directory of your starter project, create a new file named products.controller.ts. Don’t worry about putting it in a sub-folder just yet as we will take care of code organization when we reach the topic of modules in NestJS.

Within the file, paste the below code.

import { Controller, Delete, Get, Post, Put } from "@nestjs/common";

@Controller('products')
export class ProductsController {

    @Get()
    getAllProducts(): string {
        return 'This endpoint returns a list of all products';
    }

    @Post()
    createProduct(): string {
        return 'This endpoint creates a new product';
    }

    @Put()
    updateProduct(): string {
        return 'This endpoint updates an existing product';
    }

    @Delete()
    deleteProduct(): string {
        return 'This endpoint deletes an existing product';
    }
}

In NestJS, we use classes and decorators to create a controller.

In the context of a controller, you could think of classes as a group of routes that perform some common functionality

? The @Controller decorator in NestJS marks a class as a controller and defines a route for it, which maps incoming HTTP requests to its methods (handlers).

Internally, the decorator also ensures that the class is registered as a component in the NestJS Dependency Injection (DI) system.

Dependency Injection (DI) is a software design pattern where an object’s dependencies are passed to it by an external entity, rather than being created within the object itself. Think of it like a superhero (the object) who’s got superpowers (its own behaviour), but needs gadgets (dependencies) to use those powers effectively. Rather than going on a shopping spree to get all the gadgets, the superhero’s butler (DI system) brings everything to the superhero as and when needed. The superhero can now focus on saving the world (performing its own behaviour) without worrying about fetching its own gadgets!

? The @Controller decorator takes in a path that acts as the route prefix for the methods within the controller.

In our code example, the @Controller() decorator also has the path prefix “products” as input. This tells the NestJS routing mechanism that any incoming requests to the route /products should be sent to ProductsController class.

The path prefix is an optional parameter for the decorator. However, using it can help us group a set of related routes. Hence, I recommend using it wherever possible.

? There are 4 methods in the ProductsController class. Each method handles a particular HTTP request method.

For example, getAllProducts() handles any GET requests to /products route. Similarly, other methods handle the POST, PUT and DELETE request methods to the same route path.

? A route path is constructed by concatenating the optional prefix path in the @Controller() decorator and any path specified in the individual request handler.

For example, if the getAllProducts() method has a decorator @Get('/fetch'), the route path for the GET request will be /products/fetch.

The method name for the request handler such as getAllProducts() is arbitrary and totally up to the choice of the developer. NestJS does not attach any significance to the method name. It’s only concerned with the decorators in order to map the correct request handler for a particular request.

3 – Registering the NestJS Controller

Even though we have declared the ProductsController class, NestJS won’t pick up the class automatically during application startup.

You need to register the controller so that the dependency injection system can kick in and create an instance of the class when the application starts up.

In NestJS, controllers belong to a module. Therefore, you need to include the ProductsController in the controllers array of the AppModule. Think of it as a way of registering the controller.

Here’s the change you need to do in the app.module.ts file of your project.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsController } from './products.controller';

@Module({
  imports: [],
  controllers: [AppController, ProductsController],
  providers: [AppService],
})
export class AppModule {}

You can now start the application using the npm run start command and test out the controller by accessing the various endpoints.

For example, you can access the getAllProducts endpoint by making a request to http://localhost:3000/products.

Of course, at this point, all controllers will just return a text message. So things won’t feel particularly exciting.

4 – Understanding the NestJS Request Object

While the request handlers in our above example perform the job of handling requests, they are not that useful.

To perform anything meaningful, request handlers need access to the request details sent by the client. NestJS provides access to the request object based on the underlying platform.

To access the request object, you can instruct NestJS to inject it by adding the @Req() decorator to the request handler’s signature. Check out the below example:

import { Controller, Delete, Get, Post, Put, Req } from "@nestjs/common";
import { Request } from "express";

@Controller('products')
export class ProductsController {

    @Get()
    getAllProducts(@Req() request: Request): string {
        console.log(request.headers)
        return 'This endpoint returns a list of all products';
    }
}

Here, the Request class is imported from express.

The @Req() decorator binds the incoming request object to the request variable. You can then access the request variable within the handler.

The request object represents the HTTP request. It has properties for the request query string, parameters, HTTP headers and also the request body.

While you can extract these values manually from the request object, NestJS also provides dedicated decorators to make your life easy.

The below table shows some of the important decorators you can use to deal with the request object.

Decorators in NestJS

While there are other decorators as well, these are the ones that are used most frequently.

Let us look at a few of them in more detail.

? The @Param() Decorator

It is a very common requirement to accept dynamic data as part of the incoming request.

For example, if you want to fetch the product information for product id 1, your request’s path would be something like GET /products/1. Here, “1” is a route parameter token.

In this case, the route handler must be able to deal with dynamic parameters in the route. Ideally, you want to get access to the dynamic id and do something with it inside the request handler.

This is where the @Param() decorator comes in handy.

See the below example:

import { Controller, Delete, Get, Param, Post, Put, Req } from "@nestjs/common";

@Controller('products')
export class ProductsController {

    @Get(':id')
    getProduct(@Param() params): string {
        console.log(params.id);
        return `This action returns the product details for product id #${params.id}`;
    }
}

As you can see, @Param() decorates the method parameter known as params. Internally, this binds the route parameters as properties of the params object.

You can then easily access the params object within the request handler. For example, accessing the params.id property.

Param itself is imported from the @nestjs/common package.

There is another alternative way of extracting the route parameters.

Check out the snippet below:

import { Controller, Delete, Get, Param, Post, Put, Req } from "@nestjs/common";

@Controller('products')
export class ProductsController {

    @Get(':id')
    getProduct(@Param('id') id: string): string {
        console.log(id);
        return `This action returns the product details for product id #${id}`;
    }
}

In the above example, you pass the parameter’s token name i.e. ‘id’ to the @Param() decorator to directly get access by name inside the method body.

In my opinion, this approach has better readability when compared to the previous one.

? The Body() Decorator

Path parameters are good for single fields. You don’t want to be using them to transfer multiple fields. It will make the route path difficult to manage.

To deal with multiple fields, you must use the request body.

NestJS provides the @Body() decorator to extract the request body from the HTTP request object.

Check out the below example:

import { Body, Controller, Delete, Get, Param, Post, Put, Req } from "@nestjs/common";
import { Request } from "express";

@Controller('products')
export class ProductsController {

    @Post()
    createProduct(@Body() productData: any): string {
        console.log(productData);
        return 'This endpoint creates a new product';
    }
}

The @Body() decorator is used for the productData variable.

At this point, you can simply define the type of productData variable as any. In a later section of the book, I will show you how you can define Typescript classes to map the request body to a specific schema.

5 – Understanding the NestJS Response Object

NestJS automatically handles the response part depending on the underlying platform.

For example, when you return a JavaScript object, NestJS will automatically serialize it to JSON format. This is the default approach.

Also, in case of a request handler returning primitive types such as string, boolean or number, NestJS will just send the value without serializing it.

This makes it quite easy to handle responses. All you have to do is return the value and NestJS will take care of the rest.

However, you can also tap into the library-specific response object if needed. This can be done by using the @Res() decorator to inject the response object in the method handler.

See the below example:

import { Body, Controller, Delete, Get, Param, Post, Put, Req, Res } from "@nestjs/common";
import { Request } from "express";

@Controller('products')
export class ProductsController {

    @Get()
    getAllProducts(@Req() request: Request, @Res() response): any {
        console.log(request.headers)
        response.status(200).send('This endpoint returns a list of all products')
    }
}

In the above snippet, you are using the native response handling approach of Express.

When we use @Res() decorator to activate the library-specific option, the standard NestJS approach is automatically disabled for that particular route. However, we can still use both approaches. To do so, we have to set the passthrough option to true in the @Res({ passthrough: true }) decorator. Using both approaches is useful when we want to use the platform-specific response object for setting a few special fields such as cookies or headers and still delegate the rest of response handling to NestJS.

? Response Status Code

At first glance, it might seem that NestJS does not provide any way to set a specific HTTP Status Code for the response.

By default, GET methods return an HTTP Status Code of 200 and POST methods return 201.

However, despite automatically handling responses, NestJS also provides a special decorator @HttpCode() to provide a specific status code.

Check out the below example:

@Post()
@HttpCode(204)
createProduct(@Body() productData: any): string {
    console.log(productData);
    return 'This endpoint creates a new flight';
}

The @HttpCode() decorator is part of the @nestjs/common package.

An important point to note here is that the status code of a particular method is not usually static. It can depend on various factors. In such cases, you can use a library-specific response object using @Res() or throw an exception of some sort. More on that in later sections of the book.

? Response Headers

Similar to HTTP Status Code, you can also specify a custom response header.

You can either use the library-specific response object using res.header(). Alternatively, NestJS also provides a special decorator @Header().

@Get()
@Header('Content-Type', 'application/json')
getAllFlights(@Req() request: Request, @Res() response): any {
    response.status(200).send('This endpoint returns a list of all flights');
}

The @Header() decorator is imported from the @nestjs/common package.

What’s Next?

The code discussed in this post is available on GitHub. You can download it and play around with it to practice more stuff.

Here’s a summary of what you have learnt:

  • The role of a controller in NestJS
  • Writing your first NestJS controller from scratch
  • Understanding the request and response objects
  • Request and Response related decorators

In the next post, you are going to take another big step forward and learn how to handle business logic in your application using NestJS Providers.

You can also check out this post where I explain how to have multiple routes with a single NestJS controller.

In case of any queries or comments about the points discussed in this post, please feel free to write in the comments section below.

Categories: BlogNestJS

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *