title: 'Controllers · Nael Framework' description: 'Learn how to create and use controllers to handle incoming requests in Nael Framework.'

Overview

Controllers

Controllers are responsible for handling incoming requests and returning responses to the client. A controller's purpose is to receive specific requests for the application. The routing mechanism controls which controller receives which requests.

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 users. Using a path prefix in a @Controller() decorator allows us to easily group a set of related routes and minimize repetitive code.

users.controller.ts
import { Controller, Get } from '@nl-framework/http';@Controller('users')export class UsersController {  @Get()  findAll(): string {    return 'This action returns all users';  }}

The @Get() HTTP request method decorator before the findAll() method tells Nael to create a handler for a specific endpoint for HTTP requests. The endpoint corresponds to the HTTP request method (GET in this case) and the route path.

Request object

Handlers often need access to the client request details. Nael provides access to the request object. We can access the request object by instructing Nael to inject it by adding the @Req() decorator to the handler's signature.

Using request object
import { Req } from '@nl-framework/http';import { Request } from '@nl-framework/platform';@Get()findAll(@Req() request: Request) {  return request.headers;}

The request object represents the HTTP request and has properties for the request query string, parameters, HTTP headers, and body. In most cases, it's not necessary to grab these properties manually. We can use dedicated decorators instead, such as @Body() or @Query().

Resources

Earlier, we defined an endpoint to fetch users (GET route). We'll typically also want to provide an endpoint that creates new records. For this, let's create the POST handler:

POST handler
import { Controller, Get, Post } from '@nl-framework/http';@Controller('users')export class UsersController {  @Post()  create(): string {    return 'This action adds a new user';  }  @Get()  findAll(): string {    return 'This action returns all users';  }}

Nael 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 supported as well. For instance, the asterisk is used as a wildcard, and will match any combination of characters:

Wildcard route
@Get('ab*cd')findAll() {  return 'This route uses a wildcard';}

The 'ab*cd' route path will match abcd, ab_cd, abecd, and so on. The characters ?, +, *, and () may be used in a route path, and are subsets of their regular expression counterparts.

Status code

As mentioned, the response status code is always 200 by default, except for POST requests which use 201. We can easily change this behavior by adding the @HttpCode(...) decorator at a handler level.

Custom status code
import { Post, HttpCode } from '@nl-framework/http';@Post()@HttpCode(204)create() {  return 'This action adds a new user';}

Headers

To specify a custom response header, you can either use a @Header() decorator or a library-specific response object.

Custom headers
import { Post, Header } from '@nl-framework/http';@Post()@Header('Cache-Control', 'no-store')create() {  return 'This action adds a new user';}

Redirection

To redirect a response to a specific URL, you can either use a @Redirect() decorator or a library-specific response object.

Static redirect
import { Get, Redirect } from '@nl-framework/http';@Get()@Redirect('https://nael.dev', 301)redirect() {}

@Redirect() takes two arguments, url and statusCode, both are optional. The default value of statusCode is 302 (Found) if omitted.

Dynamic redirect
@Get('docs')@Redirect('https://docs.nael.dev', 302)getDocs(@Query('version') version) {  if (version && version === '5') {    return { url: 'https://docs.nael.dev/v5' };  }}

Route parameters

Routes with static paths won't work when you need to accept dynamic data as part of the request (e.g., GET /users/1 to get user with id 1). In order to define routes with parameters, we can add route parameter tokens in the path of the route to capture the dynamic value at that position in the request URL.

Route parameters
@Get(':id')findOne(@Param('id') id: string): string {  return `This action returns user #${id}`;}

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.

Sub-domain routing
@Controller({ host: ':account.example.com' })export class AccountController {  @Get()  getInfo(@HostParam('account') account: string) {    return account;  }}

Asynchronicity

Nael supports async functions. Every async function has to return a Promise. This means that you can return a deferred value that Nael will be able to resolve by itself.

Async controller
@Get()async findAll(): Promise<User[]> {  return await this.usersService.findAll();}

Furthermore, Nael route handlers are even more powerful by being able to return Bun streams. Nael will automatically resolve the stream.

Request payloads

Our previous example of the POST route handler didn't accept any client params. Let's fix this by adding the @Body() decorator here.

Request body
import { Body, Post } from '@nl-framework/http';class CreateUserDto {  name: string;  email: string;  age: number;}@Post()create(@Body() createUserDto: CreateUserDto) {  return 'This action adds a new user';}

We use TypeScript classes to define our DTO (Data Transfer Object) schema. We recommend using classes because they preserve type information at runtime, which is useful for validation with pipes.

Full resource example

Full CRUD controller
import { Controller, Get, Post, Put, Delete, Patch } from '@nl-framework/http';@Controller('users')export class UsersController {  @Post()  create() {}  @Get()  findAll() {}  @Get(':id')  findOne(@Param('id') id: string) {    return `This action returns user #${id}`;  }  @Put(':id')  update() {}  @Delete(':id')  remove() {}  @Patch(':id')  patch() {}}

With the above controller fully defined, Nael knows exactly how to map every route. Note that Nael employs dependency injection, so we can inject the UsersService into the controller's constructor.

What's next?

Learn about Providers to handle business logic, or explore Modules to organize your application structure.