Providers

Injectable services, repositories, and the dependency injection system.

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:

ScopeBehaviorUse case
singleton (default)One instance for the app lifetimeServices, config, repositories
transientNew instance on every injectionStateless utilities, factories
requestNew instance per HTTP requestRequest-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()
  }
}
HookWhen calledScope
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')