Using Actions as Controllers
Implement AsController to turn an action into an AdonisJS route handler, validate the request from HttpContext and inject services through the container
The AsController interface allows you to use actions as HTTP route handlers. This keeps your routes file clean while centralizing business logic in your actions.
Basic Example
Implement the AsController interface and define the asController method:
import { BaseAction, type AsController } from '@foadonis/actions'
import { type HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'
import Post from '#models/post'
import { createPostValidator } from '#validators/post'
export default class CreatePostAction extends BaseAction implements AsController {
async handle(title: string, content: string, authorId: number) {
const post = await Post.create({
title,
content,
authorId,
})
return post
}
async asController({ request, response, auth }: HttpContext) {
const { title, content } = await request.validateUsing(createPostValidator)
const post = await this.handle(title, content, auth.user!.id)
return response.created(post)
}
}Register the route:
import { actions } from '#generated/actions'
import router from '@adonisjs/core/services/router'
router.post('/posts', actions.CreatePost.asController())Accessing Request Data
The asController method receives the full HttpContext, giving you access to all request data. Use validateUsing to validate both body and route parameters:
import { BaseAction, type AsController } from '@foadonis/actions'
import { type HttpContext } from '@adonisjs/core/http'
import Post from '#models/post'
import { updatePostValidator } from '#validators/post'
export default class UpdatePostAction extends BaseAction implements AsController {
async handle(postId: number, data: { title?: string; content?: string }) {
const post = await Post.findOrFail(postId)
post.merge(data)
await post.save()
return post
}
async asController({ request, response }: HttpContext) {
const { params, ...data } = await request.validateUsing(updatePostValidator)
const post = await this.handle(params.id, data)
return response.ok(post)
}
}import { actions } from '#generated/actions'
import router from '@adonisjs/core/services/router'
router.put('/posts/:id', actions.UpdatePost.asController())Dependency Injection
Actions are resolved through the AdonisJS container, allowing you to inject services into the constructor:
import { BaseAction, type AsController } from '@foadonis/actions'
import { type HttpContext } from '@adonisjs/core/http'
import { inject } from '@adonisjs/core'
import PaymentService from '#services/payment_service'
import Order from '#models/order'
import { createOrderValidator } from '#validators/order'
@inject()
export default class CreateOrderAction extends BaseAction implements AsController {
constructor(private paymentService: PaymentService) {
super()
}
async handle(userId: number, items: { productId: number; quantity: number }[]) {
const order = await Order.create({ userId, status: 'pending' })
await order.related('items').createMany(items)
await this.paymentService.initializePayment(order)
return order
}
async asController({ request, response, auth }: HttpContext) {
const { items } = await request.validateUsing(createOrderValidator)
const order = await this.handle(auth.user!.id, items)
return response.created(order)
}
}Best Practices
Naming conventions, folder layout and composition patterns for actions, plus keeping business logic in handle and calling actions instead of emitting events
Using Actions as Commands
The AsCommand interface registers an action as an Ace command: customize commandName and description, then use prompts and logger inside asCommand