Service Providers
Providers are the central place for configuring and bootstrapping services. Every tekir package ships a provider that registers itself in the DI container.
Introduction
A service provider is a class with up to three lifecycle methods: register(), boot(), and shutdown(). Providers are the glue between your configuration and the application: they read config, create instances, and bind them to the container so the rest of your app can resolve them via service() or app.use().
Creating a Provider
Generate a provider with the CLI:
tekir make:provider StripeA provider is a plain class, no base class or interface import needed:
import type { App } from '@tekir/core'
export class StripeProvider {
async register(app: App) {
const config = app.use('config')
const stripeConfig = config('stripe')
if (!stripeConfig) return
const stripe = new Stripe(stripeConfig.secretKey)
app.instance('stripe', stripe)
}
async boot(app: App) {
// Called after ALL providers have registered
// Safe to resolve services from other providers
const logger = app.use('logger')
logger.info('Stripe initialized')
}
async shutdown(app: App) {
// Cleanup on application shutdown
}
}Lifecycle
Providers run in two phases. First, every provider's register() runs. Then, every provider's boot() runs. This guarantees that by the time boot() is called, all services from all providers are available.
// Provider lifecycle:
//
// 1. REGISTER phase: all providers' register() run first
// StripeProvider.register(app) → binds 'stripe' service
// MailProvider.register(app) → binds 'mail' service
//
// 2. BOOT phase: all providers' boot() run after register
// StripeProvider.boot(app) → can use app.use('mail')
// MailProvider.boot(app) → can use app.use('stripe')
//
// 3. SHUTDOWN phase: on app.shutdown(), reverse order
// MailProvider.shutdown(app) → cleanup mail
// StripeProvider.shutdown(app) → cleanup striperegister()
Use register() to bind services into the container. Do not resolve services from other providers here; they may not be registered yet.
boot()
Use boot() for logic that depends on other services. By this point, all providers have registered, so app.use() is safe to call.
shutdown()
Called when the application shuts down (e.g. SIGTERM). Providers are shut down in reverse order: the last registered provider shuts down first. Use this to close connections, flush buffers, or clean up resources.
Registering Providers
Register providers in start/kernel.ts via app.registerAll(). Pass the class itself, the framework instantiates it.
import type { TekirApp } from '@tekir/core'
import { DatabaseProvider } from '@tekir/db'
import { CacheProvider } from '@tekir/cache'
import { ViewProvider } from '@tekir/view'
import { StripeProvider } from '~/providers/stripe_provider'
export default function({ app }: TekirApp) {
app.registerAll([
DatabaseProvider,
CacheProvider,
ViewProvider,
StripeProvider
])
}The Container
Providers interact with the app container using three methods:
import type { App } from '@tekir/core'
export class PaymentProvider {
register(app: App) {
// instance(): register a ready-to-use value
app.instance('stripe', new Stripe(key))
// singleton(): factory called once, result cached
app.singleton('payments', () => {
const stripe = app.use('stripe')
return new PaymentService(stripe)
})
// bind(): factory called every time (new instance each resolve)
app.bind('invoice', () => new Invoice())
}
}app.instance(name, value): register a ready valueapp.singleton(name, factory): lazy, created once on firstuse()app.bind(name, factory): new instance everyuse()call
Resolve services with app.use(name) or via the service() lazy proxy in services.ts:
import { service } from '@tekir/core'
import type { PaymentService } from '~/services/payment'
// In services.ts: lazy proxy, resolved on first access
export const payments = service<PaymentService>('payments')
// In a handler
import { payments } from '#services'
await payments.charge(user, amount)Providers can skip registration when config is missing:
import type { App } from '@tekir/core'
export class RedisProvider {
register(app: App) {
const config = app.use('config')
// Skip if redis not configured
if (!config('redis')) return
app.singleton('redis', () => {
return new Redis(config('redis'))
})
}
}Built-in Providers
Every @tekir/* package that needs container access ships a provider:
// Framework packages that ship with providers:
//
// @tekir/db → DatabaseProvider → registers 'db'
// @tekir/cache → CacheProvider → registers 'cache'
// @tekir/auth → AuthProvider → registers 'auth'
// @tekir/session → SessionProvider → registers session middleware
// @tekir/view → ViewProvider → registers 'view'
// @tekir/hash → HashProvider → registers 'hash'
// @tekir/logger → LoggerProvider → registers 'logger'
// @tekir/redis → RedisProvider → registers 'redis'
// @tekir/static → StaticProvider → registers static middleware
// @tekir/mail → MailProvider → registers 'mail'
// @tekir/queue → QueueProvider → registers 'queue'