Skip to main content
Version: 4.2

Ktor DI Bridge

Koin 4.2+ provides seamless integration with Ktor 3.4+ built-in dependency injection system through a configurable bridge.

Experimental Feature

The Ktor DI Bridge is an experimental feature that enables bidirectional dependency resolution between Koin and Ktor DI.

Bridge Configuration

Use the bridge { } DSL to enable bidirectional dependency resolution:

fun Application.module() {
install(Koin) {
slf4jLogger()

bridge {
ktorToKoin() // Ktor DI can resolve Koin dependencies
koinToKtor() // Koin can resolve Ktor DI dependencies
}

modules(appModule)
}

// Ktor DI dependencies
dependencies {
provide<KtorSpecificService> { KtorSpecificServiceImpl() }
}
}

Bridge Options

OptionDescription
ktorToKoin()Allows Ktor's by dependencies delegate to resolve from Koin
koinToKtor()Allows Koin's inject() and get() to resolve from Ktor DI

Using ktorToKoin()

Resolve Koin dependencies using Ktor's by dependencies delegate:

fun Application.module() {
install(Koin) {
bridge {
ktorToKoin()
}
modules(module {
single<HelloService> { HelloServiceImpl() }
})
}

routing {
get("/hello") {
// Resolve Koin dependency using Ktor's delegate
val helloService: HelloService by dependencies
call.respondText(helloService.sayHello())
}
}
}

Using koinToKtor()

Resolve Ktor DI dependencies using Koin's inject():

fun Application.module() {
install(Koin) {
bridge {
koinToKtor()
}
modules(appModule)
}

// Declare in Ktor DI
dependencies {
provide<DatabaseConnection> { DatabaseConnectionImpl() }
}

routing {
get("/data") {
// Resolve Ktor DI dependency using Koin
val database: DatabaseConnection by inject()
call.respondText(database.query())
}
}
}

Full Bidirectional Example

Enable both directions for maximum flexibility:

fun Application.module() {
install(Koin) {
slf4jLogger()

bridge {
ktorToKoin()
koinToKtor()
}

modules(module {
single<HelloService> { HelloServiceImpl() }
})
}

dependencies {
provide<KtorSpecificService> { KtorSpecificServiceImpl() }
}

routing {
// Using Koin's inject() for both
get("/mixed-koin") {
val helloService: HelloService by inject() // From Koin
val ktorService: KtorSpecificService by inject() // From Ktor DI
call.respondText("${helloService.sayHello()} - ${ktorService.process()}")
}

// Using Ktor's dependencies delegate for both
get("/mixed-ktor") {
val helloService: HelloService by dependencies // From Koin
val ktorService: KtorSpecificService by dependencies // From Ktor DI
call.respondText("${helloService.sayHello()} - ${ktorService.process()}")
}
}
}

Architecture Pattern

Separate infrastructure from application logic:

  • Ktor DI - Framework-level dependencies (Database, Configuration, Infrastructure)
  • Koin - Application-level dependencies (Repositories, Services, Use Cases)
fun Application.module() {
// Ktor DI - Infrastructure layer
val config = environment.config
val database = Database(config)
dependencies {
provide<Database> { database }
provide<ApplicationConfig> { config }
}

// Koin - Application layer
install(Koin) {
slf4jLogger()

bridge {
koinToKtor() // Koin can resolve infrastructure deps
}

modules(appModule)
}
}

val appModule = module {
// These can inject Database from Ktor DI via bridge
singleOf(::CustomerRepository)
singleOf(::OrderRepository)
singleOf(::CustomerService)
}

Best Practices

  1. Infrastructure in Ktor DI - Database connections, configuration, external clients
  2. Business logic in Koin - Repositories, services, use cases
  3. Enable only needed direction - Use koinToKtor() only, not both, unless necessary
  4. Document the boundary - Make it clear which system owns which dependencies

With Isolated Context

The bridge also works with KoinIsolated:

fun Application.module() {
dependencies {
provide<Database> { Database(environment.config) }
}

install(KoinIsolated) {
slf4jLogger()

bridge {
koinToKtor()
}

modules(appModule)
}
}

See Also