Chapter 10. Routing and request handling in Nest.js

Routing and request handling in Nest.js is handled by the controllers layer. Nest.js routes requests to handler methods, which are defined inside controller classes. Adding a routing decorator such as @Get() to a method in a controller tells Nest.js to create an endpoint for this route path and route every corresponding request to this handler.

In this chapter, we’ll go over the various aspects of routing and request handling in Nest.js using the EntryController from our blog application as a basis for some examples. We’ll be looking at different approaches that you can use to write request handlers, so not all examples shown will match code from our blog application.

Request handlers

A basic GET request handler for the /entries route registered in the EntryController could look like this:

import { Controller, Get } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get()
    index(): Entry[] {
        const entries: Entry[] = this.entriesService.findAll();
        return entries;
    }

The @Controller('entries') decorator tells Nest.js to add an entries prefix to all routes registered in the class. This prefix is optional. An equivalent way to setup this route would be as follows:

import { Controller, Get } from '@nestjs/common';

@Controller()
export class EntryController {
    @Get('entries')
    index(): Entry[] {
        const entries: Entry[] = this.entriesService.findAll();
        return entries;
    }

Here, we don’t specify a prefix in the @Controller() decorator, but instead use the full route path in the @Get('entries') decorator.

In both cases, Nest.js will route all GET requests to /entries to the index() method in this controller. The array of entries returned from the handler will be automatically serialized to JSON and sent as the response body, and the response status code will be 200. This is the standard approach of generating a response in Nest.js.

Nest.js also provides the @Put(), @Delete(), @Patch(), @Options(), and @Head() decorators to create handlers for other HTTP methods. The @All() decorator tells Nest.js to route all HTTP methods for a given route path to the handler.

Generating responses

Nest.js provides two approaches for generating responses.

Standard approach

Using the standard and recommended approach, which has been available since Nest.js 4, Nest.js will automatically serialize the JavaScript object or array returned from the handler method to JSON and send it in the response body. If a string is returned, Nest.js will just send the string without serializing it to JSON.

The default response status code is 200, expect for POST requests, which uses 201. The response code for can easily be changed for a handler method by using the @HttpCode(...) decorator. For example:

@HttpCode(204)
@Post()
create() {
  // This handler will return a 204 status response
}

Express approach

An alternate approach to generating responses in Nest.js is to use a response object directly. You can ask Nest.js to inject a response object into a handler method using the @Res() decorator. Nest.js uses express response objects].

You can rewrite the response handler seen earlier using a response object as shown here.

import { Controller, Get, Res } from '@nestjs/common';
import { Response } from 'express';

@Controller('entries')
export class EntryController {
    @Get()
    index(@Res() res: Response) {
        const entries: Entry[] = this.entriesService.findAll();
        return res.status(HttpStatus.OK).json(entries);
    }
}

The express response object is used directly to serialize the entries array to JSON and send a 200 status code response.

The typings for the Response object come from express. Add the @types/express package to your devDependencies in package.json to use these typings.

Route parameters

Nest.js makes it easy to accept parameters from the route path. To do so, you simply specify route parameters in the path of the route as shown below.

import { Controller, Get, Param } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get(':entryId')
    show(@Param() params) {
        const entry: Entry = this.entriesService.find(params.entryId);
        return entry;
    }
}

The route path for the above handler method above is /entries/:entryId, with the entries portion coming from the controller router prefix and the :entryId parameter denoted by a colon. The @Param() decorator is used to inject the params object, which contains the parameter values.

Alternately, you can inject individual param values using the @Param() decorator with the parameter named specified as shown here.

import { Controller, Get, Param } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get(':entryId')
    show(@Param('entryId') entryId) {
        const entry: Entry = this.entriesService.findOne(entryId);
        return entry;
    }
}

Request body

To access the body of a request, use the @Body() decorator.

import { Body, Controller, Post } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Post()
    create(@Body() body: Entry) {
        this.entryService.create(body);
    }
}

Request object

To access the client request details, you can ask Nest.js to inject the request object into a handler using the @Req() decorator. Nest.js uses express request objects.

For example,

import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';

@Controller('entries')
export class EntryController {
    @Get()
    index(@Req() req: Request): Entry[] {
        const entries: Entry[] = this.entriesService.findAll();
        return entries;
    }

The typings for the Request object come from express. Add the @types/express package to your devDependencies in package.json to use these typings.

Asynchronous handlers

All of the examples shown so far in this chapter assume that handlers are synchronous. In a real application, many handlers will need to be asynchronous.

Nest.js provides a number of approaches to write asynchronous request handlers.

Async/await

Nest.js has support for async request handler functions.

In our example application, the entriesService.findAll() function actually returns a Promise<Entry[]>. Using async and await, this function could be written as follows.

import { Controller, Get } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get()
    async index(): Promise<Entry[]> {
        const entries: Entry[] = await this.entryService.findAll();
        return entries;
    }

Async functions have to return promises, but using the async/await pattern in modern JavaScript, the handler function can appear to be synchronous. Next, we’ll resolve the returned promise and generate the response.

Promise

Similarly, you can also just return a promise from a handler function directly without using async/await.

import { Controller, Get } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get()
    index(): Promise<Entry[]> {
        const entriesPromise: Promise<Entry[]> = this.entryService.findAll();
        return entriesPromise;
    }

Observables

Nest.js request handlers can also return RxJS Observables.

For example, if entryService.findAll() were to return an Observable of entries instead of a Promise, the following would be completely valid.

import { Controller, Get } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Get()
    index(): Observable<Entry[]> {
        const entriesPromise: Observable<Entry[]> = this.entryService.findAll();
        return entriesPromise;
    }

There is no recommended way to write asynchronous request handlers. Use whichever method you are most comfortable with.

Error responses

Nest.js has an exception layer, which is responsible for catching unhandled exceptions from request handlers and returning an appropriate response to the client.

A global exception filter handles all exception thrown from request handlers.

HttpException

If an exception thrown from a request handler is a HttpException, the global exception filter will transform it to the a JSON response.

For example, you can throw an HttpException from the create() handler function if the body is not valid as shown.

import { Body, Controller, HttpException, HttpStatus, Post } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Post()
    create(@Body() entry: Entry) {
        if (!entry) throw new HttpException('Bad request', HttpStatus.BAD_REQUEST);
        this.entryService.create(entry);
    }
}

If this exception is thrown, the response would look like this:

{
    "statusCode": 400,
    "message": "Bad request"
}

You can also completely override the response body by passing an object to the HttpException constructor as follows.

import { Body, Controller, HttpException, HttpStatus, Post } from '@nestjs/common';

@Controller('entries')
export class EntryController {
    @Post()
    create(@Body() entry: Entry) {
        if (!entry) throw new HttpException({ status: HttpStatus.BAD_REQUEST, error: 'Entry required' });
        this.entryService.create(entry);
    }
}

If this exception is thrown, the response would look like this:

{
    "statusCode": 400,
    "error": "Entry required"
}

Unrecognized exceptions

If the exception is not recognized, meaning it is not HttpException or a class that inherits from HttpException, then the client will receive the JSON response below.

{
    "statusCode": 500,
    "message": "Internal server error"
}

Summary

With the help of using the EntryController from our example blog application, this chapter has covered aspects of routing and request handling in Nest.js. You should now understand various approaches that you can use to write request handlers.

In the next chapter we detail the OpenAPI specification, which is a JSON schema that can be used to construct a JSON or YAML definition of a set of restful APIs.