Controllers
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 app/Http/Controllers
directory.
Writing controllers
Let's take a look at an example:
export class UserController {
/**
* Use the constructor to resolve any dependency of the Ioc container.
*/
constructor() {}
/**
* Show the user.
*
* @params {import('@athenna/http').ContextContract} ctx
*/
show({ response, params }) {
return response.send(`User_${params.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 app/Http/Controllers/PhotoController
. The controller will contain a method
for each of the available resource operations. Next, you may register a resource route that points to the controller:
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.
node artisan route:list
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 in its constructor. The declared dependencies
will automatically be resolved and injected into the controller instance:
export class UserController {
/** @type {import('#app/Services/UserService').UserService} */
#userService
/**
* Use the constructor to resolve any dependency of the Ioc container.
*/
constructor(userService) {
this.#userService = userService
}
/**
* Show the user.
*
* @params {import('@athenna/http').ContextContract} ctx
*/
async show({ response, params }) {
const body = await this.#userService.findOne(params.id)
return response.send(body)
}
}