Container Services
This guide covers container services in AdonisJS. You will learn:
- What container services are and how they work
- How to use existing services in your application
- When to use services versus dependency injection
- How to create your own services for packages
Overview
Container services are a convenience pattern in AdonisJS that simplifies how you access framework functionality. When you need to use features like routing, hashing, or logging, you can import a ready-to-use instance instead of manually constructing classes or interacting with the IoC container directly.
This pattern exists because many framework components require dependencies that the IoC container already knows how to provide. Rather than making you resolve these dependencies yourself in every file, AdonisJS packages expose pre-configured instances as standard ES module exports. You import them like any other module, and they work immediately.
Understanding container services
Without container services, you have two options for using framework classes. You could import a class and construct it yourself, manually providing all its dependencies.
import { Router } from '@adonisjs/core/http'
export const router = new Router(/** Router dependencies */)
Alternatively, you could use the IoC container's make method to construct the class, letting the container handle dependency resolution.
import app from '@adonisjs/core/services/app'
import { Router } from '@adonisjs/core/http'
export const router = await app.make(Router)
Container services eliminate this ceremony by doing exactly what the second approach does, but packaging it as a convenient import. The service module uses the IoC container internally and exports the resolved instance.
import router from '@adonisjs/core/services/router'
import hash from '@adonisjs/core/services/hash'
import logger from '@adonisjs/core/services/logger'
When you import a service, you're getting a singleton instance that was constructed by the IoC container with all its dependencies properly injected. The service itself is just a thin wrapper that makes this instance available as a standard module export.
Using container services
Container services are available automatically when you install AdonisJS packages. No configuration or registration is required. You simply import the service and use it.
Here's an example using the Drive service to upload a file to S3.
import drive from '@adonisjs/drive/services/main'
export class PostsController {
async store(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
/**
* The drive service gives you direct access to the
* DriveManager instance. Use it to select a disk
* and perform file operations.
*/
const disk = drive.use('s3')
await disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}
This approach is straightforward and requires no setup beyond importing the service. The Drive service is a singleton, so the same instance is shared across your entire application.
Using dependency injection instead
For applications that prefer dependency injection, you can inject the underlying class directly into your services or controllers. This approach makes your code more testable since dependencies can be easily mocked or stubbed.
Here's the same file upload functionality using constructor injection.
import { Disk } from '@adonisjs/drive'
import { inject } from '@adonisjs/core'
@inject()
export class PostService {
/**
* The Disk instance is injected by the IoC container.
* This makes it easy to swap implementations during
* testing or use different disk configurations.
*/
constructor(protected disk: Disk) {
}
async save(post: Post, coverImage: File) {
const coverImageName = 'random_name.jpg'
await this.disk.put(coverImageName, coverImage)
post.coverImage = coverImageName
await post.save()
}
}
With dependency injection, the IoC container automatically resolves and injects the Disk instance. Your class declares what it needs, and the container provides it. This pattern is particularly valuable when writing business logic that needs to remain decoupled from framework specifics.
Available services
AdonisJS core and official packages expose the following container services. Each service corresponds to a container binding and provides access to the fully constructed class instance.
| Binding | Class | Service |
|---|---|---|
app
|
Application |
@adonisjs/core/services/app
|
ace
|
Kernel |
@adonisjs/core/services/kernel
|
config
|
Config |
@adonisjs/core/services/config
|
encryption
|
Encryption |
@adonisjs/core/services/encryption
|
emitter
|
Emitter |
@adonisjs/core/services/emitter
|
hash
|
HashManager |
@adonisjs/core/services/hash
|
logger
|
LoggerManager |
@adonisjs/core/services/logger
|
repl
|
Repl |
@adonisjs/core/services/repl
|
router
|
Router |
@adonisjs/core/services/router
|
server
|
Server |
@adonisjs/core/services/server
|
testUtils
|
TestUtils |
@adonisjs/core/services/test_utils
|
Creating your own services
If you're building a package or want to expose your own container bindings as services, you can follow the same pattern that AdonisJS uses internally. A container service is simply a module that resolves a binding from the container and exports it.
You can view the complete implementation on GitHub to see how the Drive package creates its service.
import app from '@adonisjs/core/services/app'
let drive: DriveManager
await app.booted(async () => {
drive = await app.container.make('drive')
})
export { drive as default }
The service waits for the application to boot, then resolves the binding from the container and exports it. This ensures all service providers have registered their bindings before the service attempts to resolve them.