Providers
Providers are the backbone of MiiaJS's dependency injection (DI) system. Any class decorated with @Injectable() can be injected into other classes.
Defining a provider
import { Injectable } from '@miiajs/core'
@Injectable()
class UserService {
findAll() {
return [{ id: 1, name: 'Alice' }]
}
findById(id: string) {
return { id, name: 'Alice' }
}
}
Register the provider in a Module:
@Module({
controllers: [UserController],
providers: [UserService],
})
class UserModule {}
Injecting dependencies
MiiaJS offers two equivalent approaches for injection.
inject() function
Call inject() in a field initializer:
import { Controller, Get, inject } from '@miiajs/core'
@Controller('/users')
class UserController {
private userService = inject(UserService)
@Get('/')
list() {
return this.userService.findAll()
}
}
@Inject() decorator
Use the @Inject() field decorator:
import { Controller, Get, Inject } from '@miiajs/core'
@Controller('/users')
class UserController {
@Inject(UserService) private userService!: UserService
@Get('/')
list() {
return this.userService.findAll()
}
}
Both approaches are functionally identical. Use whichever you prefer.
String tokens
You can register and resolve providers using string tokens instead of class references:
// Registration
container.register('API_KEY', () => process.env.API_KEY)
// Injection
class MyService {
private apiKey = inject<string>('API_KEY')
}
Scopes
Providers support three scopes:
| Scope | Behavior | Use case |
|---|---|---|
singleton (default) | One instance for the app lifetime | Services, config, repositories |
transient | New instance on every injection | Stateless utilities, factories |
request | New instance per HTTP request | Request-scoped state, loggers |
@Injectable({ scope: 'singleton' })
class DatabaseService {}
@Injectable({ scope: 'transient' })
class RequestParser {}
@Injectable({ scope: 'request' })
class RequestLogger {}
Request-scoped instances are automatically cleared after each HTTP request.
Optional injection
Use injectOptional() when a provider might not be registered:
import { injectOptional } from '@miiajs/core'
@Injectable()
class NotificationService {
private email = injectOptional(EmailService) // null if not registered
}
Lifecycle hooks
Singleton providers can implement lifecycle hooks:
@Injectable()
class DatabaseService {
async onInit() {
// Called during app.init() or on first request
await this.connect()
}
async onDestroy() {
// Called during app.destroy()
await this.disconnect()
}
}
| Hook | When called | Scope |
|---|---|---|
onInit() | During app.init() or lazily on first app.fetch() | Singletons only |
onDestroy() | During app.destroy() | Singletons only |
Factory providers
Register a provider with a custom factory function:
@Module({
providers: [
{
token: 'DATABASE',
factory: (container) => {
const url = container.resolve<string>('DATABASE_URL')
return createDatabase(url)
},
},
],
})
class AppModule {}
The Container
Each Miia instance owns its own Container. The container is accessible via app.get():
const app = new Miia().register(AppModule)
await app.init()
const userService = app.get(UserService)
const config = app.get<string>('API_KEY')