The AdonisJS application follows a well-defined lifecycle with distinct states and hooks. Understanding this lifecycle is crucial for properly initializing services, managing resources, and ensuring graceful shutdowns.
Application States
An AdonisJS application progresses through the following states:
created
The application instance has been created but not yet initialized. const app = new Application ( new URL ( '../' , import . meta . url ))
console . log ( app . getState ()) // 'created'
initiated
Configuration has been loaded and service providers have been registered. await app . init ()
console . log ( app . getState ()) // 'initiated'
booted
All service providers have been booted and their services are ready. await app . boot ()
console . log ( app . getState ()) // 'booted'
ready
The application has started and is ready to serve requests or handle commands. await app . start (() => {})
console . log ( app . getState ()) // 'ready'
terminated
The application has been gracefully shut down. await app . terminate ()
console . log ( app . getState ()) // 'terminated'
Lifecycle Methods
init()
Initializes the application by loading configuration and registering service providers:
await app . init (): Promise < void >
During initialization:
Configuration files are loaded from the config/ directory
The .adonisrc.ts file is read to discover service providers
All service providers’ register() methods are called
Services are bound to the IoC container
import { Ignitor } from '@adonisjs/core'
const ignitor = new Ignitor ( new URL ( '../' , import . meta . url ))
const app = ignitor . createApp ( 'web' )
await app . init ()
// App state is now 'initiated'
// Container has all service bindings
boot()
Boots all registered service providers:
await app . boot (): Promise < void >
During boot:
All service providers’ boot() methods are called in order
Services can resolve dependencies from the container
Cross-service initialization occurs
await app . init ()
await app . boot ()
// App state is now 'booted'
// All services are initialized and ready
The boot() method is idempotent. Calling it multiple times has no effect after the first call.
start()
Starts the application and calls the ready hooks:
await app . start ( callback : () => void | Promise < void > ): Promise < void >
During start:
The provided callback is executed
All service providers’ ready() methods are called
Application state transitions to 'ready'
await app . init ()
await app . boot ()
await app . start ( async () => {
// Application-specific startup logic
const server = await app . container . make ( 'server' )
await server . boot ()
})
// App state is now 'ready'
terminate()
Gracefully shuts down the application:
await app . terminate (): Promise < void >
During termination:
Terminating hooks are called (registered via app.terminating())
All service providers’ shutdown() methods are called
Resources are cleaned up and connections are closed
Application state transitions to 'terminated'
process . on ( 'SIGTERM' , async () => {
await app . terminate ()
process . exit ( 0 )
})
Lifecycle Hooks
You can register hooks to run at specific points in the application lifecycle:
booting()
Register a callback that runs before the application boots:
app . booting ( async () => {
console . log ( 'Application is about to boot' )
})
booted()
Register a callback that runs after the application has booted:
app . booted ( async () => {
console . log ( 'Application has booted' )
// Services are available in the container
})
ready()
Register a callback that runs when the application is ready:
app . ready ( async () => {
console . log ( 'Application is ready' )
// Application is fully started
})
terminating()
Register a callback that runs when the application is terminating:
app . terminating ( async () => {
console . log ( 'Application is terminating' )
// Clean up resources, close connections
})
Full Lifecycle Example
Here’s a complete example showing the application lifecycle in an HTTP server:
import { Ignitor } from '@adonisjs/core'
const ignitor = new Ignitor ( new URL ( '../' , import . meta . url ))
// Tap into app creation
ignitor . tap (( app ) => {
console . log ( '1. App created:' , app . getState ()) // 'created'
// Register lifecycle hooks
app . booting (() => {
console . log ( '2. App is booting' )
})
app . booted (() => {
console . log ( '4. App has booted:' , app . getState ()) // 'booted'
})
app . ready (() => {
console . log ( '6. App is ready:' , app . getState ()) // 'ready'
})
app . terminating (() => {
console . log ( '7. App is terminating' )
})
})
await ignitor . httpServer (). start ()
// Output:
// 1. App created: created
// 2. App is booting
// 3. Providers registering services (init)
// 4. App has booted: booted
// 5. Server starting
// 6. App is ready: ready
Service Provider Lifecycle
Service providers have their own lifecycle methods that align with the application lifecycle:
export default class MyServiceProvider {
constructor ( protected app : ApplicationService ) {}
/**
* Called during app.init()
* Register services with the container
*/
register () {
this . app . container . singleton ( 'myService' , () => {
return new MyService ()
})
}
/**
* Called during app.boot()
* Boot services and resolve dependencies
*/
async boot () {
const myService = await this . app . container . make ( 'myService' )
await myService . initialize ()
}
/**
* Called during app.start()
* Finalize setup when app is ready
*/
async ready () {
const myService = await this . app . container . make ( 'myService' )
myService . startBackgroundTasks ()
}
/**
* Called during app.terminate()
* Clean up resources
*/
async shutdown () {
const myService = await this . app . container . make ( 'myService' )
await myService . cleanup ()
}
}
All lifecycle methods in service providers are optional. Only implement the ones you need.
HTTP Server Lifecycle
The HTTP server has additional lifecycle events specific to web applications:
import { Ignitor } from '@adonisjs/core'
const ignitor = new Ignitor ( new URL ( '../' , import . meta . url ))
ignitor . tap ( async ( app ) => {
const emitter = await app . container . make ( 'emitter' )
emitter . on ( 'http:server_ready' , ({ host , port , duration }) => {
console . log ( `Server started on ${ host } : ${ port } ` )
console . log ( `Startup time: ${ duration [ 0 ] } s ${ duration [ 1 ] / 1000000 } ms` )
})
})
await ignitor . httpServer (). start ()
Ace Command Lifecycle
Ace commands have a conditional lifecycle based on whether they need the full application:
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
export default class MyCommand extends BaseCommand {
static commandName = 'my:command'
// This command needs the full app booted
static options : CommandOptions = {
startApp: true
}
async run () {
// App is booted and ready
const logger = await this . app . container . make ( 'logger' )
logger . info ( 'Command running' )
}
}
Commands without startApp
Commands with startApp: false (or undefined) run faster because they skip the boot phase: export default class QuickCommand extends BaseCommand {
static commandName = 'quick:command'
// App won't be booted
static options : CommandOptions = {
startApp: false
}
async run () {
// App is only initiated, not booted
// Container bindings may not be available
console . log ( 'Quick command running' )
}
}
Environment-Specific Behavior
The application lifecycle can vary slightly based on the environment:
Full lifecycle with HTTP server:
init() - Load config and register providers
boot() - Boot all providers
start() - Start HTTP server
Server listens and handles requests
terminate() - Close server and clean up
Conditional lifecycle based on command:
init() - Load config and register providers
boot() - Only if command has startApp: true
start() - Only if command has startApp: true
Execute command
terminate() - Unless command has staysAlive: true
Full lifecycle for test suite:
init() - Load config and register providers
boot() - Boot all providers
start() - Prepare for tests
Run test suite
terminate() - Clean up after tests
Interactive lifecycle:
init() - Load config and register providers
boot() - Boot all providers
start() - Start REPL session
User interacts with REPL
terminate() - User exits REPL
Graceful Shutdown
AdonisJS handles graceful shutdown automatically, but you can customize the behavior:
import { Ignitor } from '@adonisjs/core'
const ignitor = new Ignitor ( new URL ( '../' , import . meta . url ))
ignitor . tap (( app ) => {
app . terminating ( async () => {
// Close database connections
const db = await app . container . make ( 'lucid.db' )
await db . manager . closeAll ()
// Wait for pending jobs
const queue = await app . container . make ( 'queue' )
await queue . drain ()
console . log ( 'Cleanup complete' )
})
})
await ignitor . httpServer (). start ()
// Handle signals
process . on ( 'SIGTERM' , () => ignitor . terminate ())
process . on ( 'SIGINT' , () => ignitor . terminate ())
Next Steps
Service Providers Learn how to create service providers with lifecycle hooks
Container Understand the IoC container for dependency injection