Introduction

Organize your business logic into reusable, testable actions

Actions provide a structured way to organize your business logic into single-purpose, reusable classes. Instead of scattering logic across controllers, commands, and event listeners, you encapsulate it in one place and invoke it from anywhere.

The Problem

In a typical AdonisJS application, you might need to perform the same operation from multiple entry points. For example, creating a user might happen:

  • From an HTTP request (registration form)
  • From a CLI command (seeding or admin tools)
  • From an event listener (OAuth callback)

Without a clear pattern, this leads to duplicated code, inconsistent behavior, and logic that's difficult to test in isolation.

The Solution

Actions solve this by extracting your business logic into dedicated classes. Each action has a single responsibility and can be invoked from any entry point in your application.

app/actions/create_user_action.ts
import { BaseAction } from '@foadonis/actions'
import User from '#models/user'

export default class CreateUserAction extends BaseAction {
  async handle(email: string, password: string) {
    const user = await User.create({ email, password })
    return user
  }
}

This pattern is inspired by the UseCase concept from Clean Architecture, where each use case represents a single action the system can perform.

Benefits

  • Reusability — Write once, use from HTTP controllers, CLI commands, or event listeners
  • Testability — Test your business logic in isolation without HTTP context or CLI setup
  • Organization — Keep controllers thin and focused on HTTP concerns
  • Consistency — Ensure the same logic runs regardless of the entry point

How It Works

An action can serve multiple entry points through dedicated interfaces.

By implementing AsController, AsCommand, or AsListener, your action becomes usable from that entry point while keeping the core logic in the handle method.

app/actions/create_user_action.ts
import { BaseAction, type AsController } from '@foadonis/actions'
import { type HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class CreateUserAction extends BaseAction implements AsController {
  async handle(email: string, password: string) {
    const user = await User.create({ email, password })
    return user
  }

  async asController({ request, response }: HttpContext) {
    const { email, password } = request.only(['email', 'password'])
    const user = await this.handle(email, password)
    return response.created(user)
  }
}

Next Steps

Ready to get started? Head to the Getting Started guide to install and configure Actions in your AdonisJS application.

On this page