Skip to main content

Controllers & Routing

Controllers responsible for handling incoming HTTP requests and returning responses. They are defined using classes and decorators.

Defining a Controller

Use the @Controller() decorator to define a controller. You can specify an optional base path.

Simple Path Syntax

For basic controllers, you can pass the path as a string directly:

import { Controller, Get } from '@carno.js/core';

@Controller('/users')
export class UserController {

@Get()
findAll() {
return 'This action returns all users';
}
}

Object Options Syntax

For advanced configurations (scope, nested controllers), use the object syntax:

import { Controller, Get, ProviderScope } from '@carno.js/core';

@Controller({
path: '/users',
scope: ProviderScope.REQUEST
})
export class UserController {

@Get()
findAll() {
return 'This action returns all users';
}
}

Both syntaxes are equivalent when you only need to specify a path. Use the object syntax when you need additional options like scope or children.

Lifecycle

Controllers are singleton by default and are pre-instantiated at startup for faster first request handling. If a controller is marked as ProviderScope.REQUEST or depends on any request-scoped provider, it becomes request-scoped and is instantiated per request.

To register the controller, add it to the providers list in your application configuration.

new Carno({
providers: [UserController]
}).listen();

Route Methods

Carno.js supports standard HTTP methods via decorators:

  • @Get(path?)
  • @Post(path?)
  • @Put(path?)
  • @Delete(path?)
  • @Patch(path?)

The path argument is optional. If omitted, the route corresponds to the controller's base path.

@Controller('/cats')
export class CatsController {
@Post()
create() {
return 'This action adds a new cat';
}

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

Request Parameters

Use decorators to access request data.

DecoratorDescriptionExample
@Body(key?)Request body (JSON/Form)@Body() body or @Body('name') name
@Query(key?)Query string parameters@Query() q or @Query('limit') limit
@Param(key?)Route parameters@Param() params or @Param('id') id
@Headers(key?)Request headers@Headers() headers or @Headers('authorization') token
@Req()The raw Request object@Req() req
@Locals()Request-scoped locals@Locals() locals

Example

@Post(':id')
update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
@Query('verbose') verbose: string
) {
return { id, ...updateUserDto, verbose };
}

Nested Controllers (Routing Tree)

You can structure your application using nested controllers. This allows you to build a route tree where children inherit the path prefix of their parent.

@Controller({
path: '/api',
children: [UsersController, PostsController]
})
export class ApiController {}

@Controller('/users') // Final path: /api/users
export class UsersController {
@Get()
getAll() { ... }
}

Note: Middleware applied to a parent controller is inherited by its children.

Responses

JSON Response

By default, if you return an object or array, Carno.js serializes it to JSON and sets Content-Type: application/json.

@Get()
findAll() {
return { data: [] };
}

Text Response

Returning a string sends a text/html response.

Response Object

You can return a native Response object for full control.

@Get()
custom() {
return new Response('Custom', { status: 201 });
}

Listing Registered Routes

As your application grows, it can be helpful to see a complete list of all registered routes, including their methods and full paths (resolving nesting).

You can use the Carno CLI to inspect your project (see CLI Installation if you haven't installed it yet):

# Analyze carno.config.ts and list routes
carno routes

# Or point to your entry file if config is not enough
carno routes src/index.ts

This command outputs a table showing the HTTP Method, Full URI, and the Controller Action, making it easy to debug routing issues or verify your API structure.