We use cookies to enhance your experience on the site
CodeWorlds

Controllers - the centurions of the Empire

A messenger arrives at the camp gate with a request: GET /legion/5. The server received it - but who is to handle it? The application has dozens of methods and none of them knows this one is for it.

In a legion, distributing orders is the centurion's job. He stands between the messenger and the soldiers: he takes the order, recognises whom it concerns, passes it on and sends back the answer. He does not do the work himself - he directs it. In NestJS that centurion is a controller.

Declaring a controller

A controller is a class with a decorator saying which stretch of routes it handles:

1@Controller('legion')
2export class LegionController { }

The order is always the same:

@Controller('legion')
, then
export class
, and finally the class name with its body.

The

'legion'
argument is a route prefix. Every method of this class will serve addresses beginning with
/legion
- and you do not have to repeat that on each of them. A controller with no argument,
@Controller()
, takes routes from the root.

Four verbs

Inside the controller, each method gets a decorator saying which request it answers:

1@Controller('tributes')
2export class TributesController {
3  @Get()
4  findAll() { }
5
6  @Post()
7  create() { }
8
9  @Put(':id')
10  update() { }
11
12  @Delete(':id')
13  remove() { }
14}

Four decorators match four actions on a resource.

@Get()
fetches,
@Post()
creates a new resource,
@Put()
updates an existing one,
@Delete()
removes it.

Telling

@Post
from
@Put
tends to confuse, so remember them by what repetition does: sending the same
@Post
twice creates two resources, while sending the same
@Put
twice leaves one, merely written twice.

The decorator's argument adds a piece of route.

@Put(':id')
in a controller prefixed
tributes
will serve
/tributes/42
. The colon marks a parameter - a slot any value falls into.

Three places data comes from

Since a route can contain a parameter, it has to be read somehow. Data arrives in a request by three roads, and each has its own decorator:

1@Get(':id')
2findOne(@Param('id') id: string) { }
3
4@Get('search')
5search(@Query('province') province: string) { }
6
7@Post()
8create(@Body() dto: CreateTributeDto) { }

@Param('id')
pulls a value out of the address path - it is what reads
5
from
/legion/5
.
@Query('province')
takes a query parameter, what stands after the question mark:
?province=rome
.
@Body()
reaches for the request body, sent with
@Post
and
@Put
.

These three cover everything you usually need. Beware of two plausible-sounding names:

@Path()
does not exist in NestJS - the path is served by
@Param
.
@Header()
does exist but does something else
: it sets a response header. To read request headers there is
@Headers()
, in the plural.

A controller method from the inside

Let's put it together. The order of a method's elements never varies:

1@Get()
2getAllLegionaries() {
3  return this.service.findAll();
4}

First the decorator

@Get()
, then the method name, then the body in braces, and inside it
return this.service.findAll()
.

And here you see what a controller really is. This method contains no logic - it takes the request and hands it to a service. A centurion does not forge swords; he knows which smith to send to.

This is a rule I recommend keeping from day one, @name: no database queries, calculations or business rules in a controller. When a controller method grows beyond a few lines, that is a sign it is doing something belonging to a service. The gain is practical - you will later call that same logic from a batch job or a queue consumer, where there is no HTTP at all.

Note that we do not build the response by hand. The returned value will be turned into JSON, and NestJS picks the status code itself: 200 for most methods, 201 for

@Post
, because something was created.

Summary

The centurion stands at the gate and knows whom to pass the order to:

  • a controller receives a request and directs it onward - it does not do the work itself,
  • declaration in order:
    @Controller('prefix')
    ,
    export class
    , the class name,
  • the
    @Controller
    argument is a route prefix shared by every method of the class,
  • four verbs:
    @Get()
    fetches,
    @Post()
    creates a new resource
    ,
    @Put()
    updates,
    @Delete()
    removes,
  • a repeated
    @Post
    creates two resources, a repeated
    @Put
    leaves one,
  • a colon in a route marks a parameter:
    @Put(':id')
    serves
    /tributes/42
    ,
  • @Param('id')
    reads from the path
    ,
    @Query('name')
    reads a query parameter
    after the
    ?
    ,
    @Body()
    reads the request body,
  • @Path()
    does not exist
    ;
    @Header()
    sets a response header, and request headers are read by
    @Headers()
    ,
  • order inside a method: decorator, name, body,
    return this.service.findAll()
    ,
  • no business logic in a controller - that way you can call it outside HTTP too,
  • NestJS picks the status code: 200, and 201 for
    @Post
    .

In the next lesson you will meet the ones the centurion passes orders to - services, where the real logic lives. For now remember: a controller recognises a request and names its executor - and every piece of data it needs arrives by one of three roads: the path, the query, or the body.

Go to CodeWorlds