Controllers
Understand how you can set up controllers in your REST API application.
Introduction
Instead of defining all of your request handling logic as
closures in your route files, you may wish to organize this
behavior using "controller" classes. Controllers can group
related request handling logic into a single class. For
example, a UserController
class might handle all incoming
requests related to users, including showing, creating,
updating, and deleting users. By default, controllers are
stored in the
Path.controllers()
./src/http/controllers
Writing controllers
Let's take a look at an example:
import { Context, Controller } from '@athenna/http'
@Controller()
export class UserController {
public async show({ request, response }: Context) {
return response.send(`User_${request.param('id')}`)
}
}
You can define a route to this controller method like so:
Route.controller('UserController').get('/user/:id', 'show')
// or
Route.get('/user/:id', 'UserController.show')
When an incoming request matches the specified route URL,
the show
method on the UserController
class will be
invoked and the route parameters will be passed to the
method.
Resource controllers
If you think of each model in your application as a
"resource", it is typical to perform the same sets of
actions against each resource in your application. For
example, imagine your application contains a Photo
model
and a Movie
model. It is likely that users can create,
read, update, or delete these resources.
Because of this common use case, Athenna resource routing
assigns the typical creation, read, update, and delete
("CRUD") routes to a controller with a single line of code.
To get started, we can use the make:controller
command's
--resource
option to quickly create a controller to handle
these actions:
node artisan make:controller PhotoController --resource
This command will generate a controller at
Path.controllers('PhotoController.ts')
./src/http/controllers/PhotoController.ts
Route.resource('photos', 'PhotoController')
This single route declaration creates multiple routes to
handle a variety of actions on the resource. The generated
controller will already have methods stubbed for each of
these actions. Remember, you can always get a quick
overview of your application's routes by running the
route:list
command.
Partial resource routes
When declaring a resource route, you may specify a subset of actions the controller should handle instead of the full set of default actions:
Route.resource('photos', 'PhotoController').only(['index', 'show'])
Route.resource('photos', 'PhotoController').except(['store', 'update', 'destroy'])
Nested resources
Sometimes you may need to define routes to a nested resource. For example, a photo resource may have multiple comments that may be attached to the photo. To nest the resource controllers, you may use "dot" notation in your route declaration:
Route.resource('photos.comments', 'PhotoCommentController')
This route will register a nested resource that may be accessed with URLs like the following:
/photos/:photoId/comments/:commentId
Dependency injection and controllers
The Athenna service container
is used to resolve all Athenna controllers. As a result,
you are able to use any dependencies your controller may
need using @Inject()
annotation or in its constructor.
The declared dependencies will automatically be resolved
and injected into the controller instance when receiving
a request from the server:
import { Inject } from '@athenna/ioc'
import { Controller, type Context } from '@athenna/http'
import { UserService } from '#src/services/UserService'
import { ProfileService } from '#src/services/ProfileService'
@Controller()
export class UserController {
private userService: UserService
@Inject() 👈
private profileService: ProfileService
public constructor(userService) { 👈
this.userService = userService
}
public async show({ response, params }: Context) {
//
}
}
To learn more about the @Controller()
annotation, visit the
REST API annotations documentation page.