Skip to main content
version 1.0.0

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 UserControllerclass 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)
}
}