Controllers
Controllers are responsible for handling incoming requests and sending responses back to the client.

A controller's purpose is to handle specific requests for the application. The routing mechanism determines which controller will handle each request. Often, a controller has multiple routes, and each route can perform a different action.
To create a basic controller, we use classes and decorators. Decorators link classes with the necessary metadata, allowing Nest to create a routing map that connects requests to their corresponding controllers.
info Hint To quickly create a CRUD controller with built-in validation, you can use the CLI's CRUD generator:
nest g resource [name].
Routing
In the following example, we’ll use the @Controller() decorator, which is required to define a basic controller. We'll specify an optional route path prefix of cats. Using a path prefix in the @Controller() decorator helps us group related routes together and reduces repetitive code. For example, if we want to group routes that manage interactions with a cat entity under the /cats path, we can specify the cats path prefix in the @Controller() decorator. This way, we don't need to repeat that portion of the path for each route in the file.
info Hint To take advantage of
expresstypings (like in therequest: Requestparameter example above), make sure to install the@types/expresspackage.
The request object represents the HTTP request and contains properties for the query string, parameters, HTTP headers, and body (read more here). In most cases, you don't need to manually access these properties. Instead, you can use dedicated decorators like @Body() or @Query(), which are available out of the box. Below is a list of the provided decorators and the corresponding platform-specific objects they represent.
* For compatibility with typings across underlying HTTP platforms (e.g., Express and Fastify), Nest provides @Res() and @Response() decorators. @Res() is simply an alias for @Response(). Both directly expose the underlying native platform response object interface. When using them, you should also import the typings for the underlying library (e.g., @types/express) to take full advantage. Note that when you inject either @Res() or @Response() in a method handler, you put Nest into Library-specific mode for that handler, and you become responsible for managing the response. When doing so, you must issue some kind of response by making a call on the response object (e.g., res.json(...) or res.send(...)), or the HTTP server will hang.
info Hint To learn how to create your own custom decorators, visit this chapter.
Resources
Earlier, we defined an endpoint to fetch the cats resource (GET route). We'll typically also want to provide an endpoint that creates new records. For this, let's create the POST handler:
It's that simple. Nest provides decorators for all of the standard HTTP methods: @Get(), @Post(), @Put(), @Delete(), @Patch(), @Options(), and @Head(). In addition, @All() defines an endpoint that handles all of them.
Route wildcards
Pattern-based routes are also supported in NestJS. For example, the asterisk (*) can be used as a wildcard to match any combination of characters in a route at the end of a path. In the following example, the findAll() method will be executed for any route that starts with abcd/, regardless of the number of characters that follow.
The 'abcd/*' route path will match abcd/, abcd/123, abcd/abc, and so on. The hyphen ( -) and the dot (.) are interpreted literally by string-based paths.
This approach works on both Express and Fastify. However, with the latest release of Express (v5), the routing system has become more strict. In pure Express, you must use a named wildcard to make the route work—for example, abcd/*splat, where splat is simply the name of the wildcard parameter and has no special meaning. You can name it anything you like. That said, since Nest provides a compatibility layer for Express, you can still use the asterisk (*) as a wildcard.
When it comes to asterisks used in the middle of a route, Express requires named wildcards (e.g., ab{*splat}cd), while Fastify does not support them at all.
Status code
As mentioned, the default status code for responses is always 200, except for POST requests, which default to 201. You can easily change this behavior by using the @HttpCode(...) decorator at the handler level.
info Hint Import
HttpCodefrom the@nestjs/commonpackage.
Often, your status code isn't static but depends on various factors. In that case, you can use a library-specific response (inject using @Res()) object (or, in case of an error, throw an exception).
Response headers
To specify a custom response header, you can either use a @Header() decorator or a library-specific response object (and call res.header() directly).
info Hint Import
Headerfrom the@nestjs/commonpackage.
Redirection
To redirect a response to a specific URL, you can either use a @Redirect() decorator or a library-specific response object (and call res.redirect() directly).
@Redirect() takes two arguments, url and statusCode, both are optional. The default value of statusCode is 302 (Found) if omitted.
info Hint Sometimes you may want to determine the HTTP status code or the redirect URL dynamically. Do this by returning an object following the
HttpRedirectResponseinterface (from@nestjs/common).
Returned values will override any arguments passed to the @Redirect() decorator. For example:
Route parameters
Routes with static paths won’t work when you need to accept dynamic data as part of the request (e.g., GET /cats/1 to get the cat with id 1). To define routes with parameters, you can add route parameter tokens in the route path to capture the dynamic values from the URL. The route parameter token in the @Get() decorator example below illustrates this approach. These route parameters can then be accessed using the @Param() decorator, which should be added to the method signature.
info Hint Routes with parameters should be declared after any static paths. This prevents the parameterized paths from intercepting traffic destined for the static paths.
The @Param() decorator is used to decorate a method parameter (in the example above, params), making the route parameters accessible as properties of that decorated method parameter inside the method. As shown in the code, you can access the id parameter by referencing params.id. Alternatively, you can pass a specific parameter token to the decorator and directly reference the route parameter by name within the method body.
info Hint Import
Paramfrom the@nestjs/commonpackage.
Sub-domain routing
The @Controller decorator can take a host option to require that the HTTP host of the incoming requests matches some specific value.
warning Warning Since Fastify does not support nested routers, if you are using sub-domain routing, it is recommended to use the default Express adapter instead.
Similar to a route path, the host option can use tokens to capture the dynamic value at that position in the host name. The host parameter token in the @Controller() decorator example below demonstrates this usage. Host parameters declared in this way can be accessed using the @HostParam() decorator, which should be added to the method signature.
State sharing
For developers coming from other programming languages, it might be surprising to learn that in Nest, nearly everything is shared across incoming requests. This includes resources like the database connection pool, singleton services with global state, and more. It's important to understand that Node.js doesn't use the request/response Multi-Threaded Stateless Model, where each request is handled by a separate thread. As a result, using singleton instances in Nest is completely safe for our applications.
That said, there are specific edge cases where having request-based lifetimes for controllers may be necessary. Examples include per-request caching in GraphQL applications, request tracking, or implementing multi-tenancy. You can learn more about controlling injection scopes here.
Asynchronicity
We love modern JavaScript, especially its emphasis on asynchronous data handling. That’s why Nest fully supports async functions. Every async function must return a Promise, which allows you to return a deferred value that Nest can resolve automatically. Here's an example:
This code is perfectly valid. But Nest takes it a step further by allowing route handlers to return RxJS observable streams as well. Nest will handle the subscription internally and resolve the final emitted value once the stream completes.
Both approaches are valid, and you can choose the one that best suits your needs.
Request payloads
In our previous example, the POST route handler didn’t accept any client parameters. Let's fix that by adding the @Body() decorator.
Before we proceed (if you're using TypeScript), we need to define the DTO (Data Transfer Object) schema. A DTO is an object that specifies how data should be sent over the network. We could define the DTO schema using TypeScript interfaces or simple classes. However, we recommend using classes here. Why? Classes are part of the JavaScript ES6 standard, so they remain intact as real entities in the compiled JavaScript. In contrast, TypeScript interfaces are removed during transpilation, meaning Nest can't reference them at runtime. This is important because features like Pipes rely on having access to the metatype of variables at runtime, which is only possible with classes.
Let's create the CreateCatDto class:
It has only three basic properties. Thereafter we can use the newly created DTO inside the CatsController:
info Hint Our
ValidationPipecan filter out properties that should not be received by the method handler. In this case, we can whitelist the acceptable properties, and any property not included in the whitelist is automatically stripped from the resulting object. In theCreateCatDtoexample, our whitelist is thename,age, andbreedproperties. Learn more here.
Query parameters
When handling query parameters in your routes, you can use the @Query() decorator to extract them from incoming requests. Let's see how this works in practice.
Consider a route where we want to filter a list of cats based on query parameters like age and breed. First, define the query parameters in the CatsController:
In this example, the @Query() decorator is used to extract the values of age and breed from the query string. For example, a request to:
would result in age being 2 and breed being Persian.
If your application requires handling more complex query parameters, such as nested objects or arrays:
you'll need to configure your HTTP adapter (Express or Fastify) to use an appropriate query parser. In Express, you can use the extended parser, which allows for rich query objects:
In Fastify, you can use the querystringParser option:
info Hint
qsis a querystring parser that supports nesting and arrays. You can install it usingnpm install qs.
Handling errors
There's a separate chapter about handling errors (i.e., working with exceptions) here.
Full resource sample
Below is an example that demonstrates the use of several available decorators to create a basic controller. This controller provides a few methods to access and manipulate internal data.
info Hint Nest CLI offers a generator (schematic) that automatically creates all the boilerplate code, saving you from doing this manually and improving the overall developer experience. Learn more about this feature here.
Getting up and running
Even with the CatsController fully defined, Nest doesn't yet know about it and won't automatically create an instance of the class.
Controllers must always be part of a module, which is why we include the controllers array within the @Module() decorator. Since we haven’t defined any other modules apart from the root AppModule, we’ll use it to register the CatsController:
We attached the metadata to the module class using the @Module() decorator, and now Nest can easily determine which controllers need to be mounted.
Library-specific approach
So far, we've covered the standard Nest way of manipulating responses. Another approach is to use a library-specific response object. To inject a specific response object, we can use the @Res() decorator. To highlight the differences, let’s rewrite the CatsController like this:
While this approach works and offers more flexibility by giving full control over the response object (such as header manipulation and access to library-specific features), it should be used with caution. Generally, this method is less clear and comes with some downsides. The main disadvantage is that your code becomes platform-dependent, as different underlying libraries may have different APIs for the response object. Additionally, it can make testing more challenging, as you'll need to mock the response object, among other things.
Furthermore, by using this approach, you lose compatibility with Nest features that rely on standard response handling, such as Interceptors and the @HttpCode() / @Header() decorators. To address this, you can enable the passthrough option like this:
With this approach, you can interact with the native response object (for example, setting cookies or headers based on specific conditions), while still allowing the framework to handle the rest.

