docs/reference/koin-ktor/ktor-scopes.md
Request scopes create instances that live for the duration of a single HTTP request, perfect for request-specific data and processing.
Use requestScope to declare components bound to the request lifecycle:
val appModule = module {
// Singleton - shared across all requests
single { UserRepository() }
// Request scope - new instance per request
requestScope {
scopedOf(::RequestLogger)
scopedOf(::RequestMetrics)
scopedOf(::UserSessionHandler)
}
}
Use call.scope.get() to resolve request-scoped dependencies:
routing {
get("/users/{id}") {
val requestLogger = call.scope.get<RequestLogger>()
val metrics = call.scope.get<RequestMetrics>()
metrics.start()
requestLogger.log("Processing user request")
val userId = call.parameters["id"]!!
val userService = get<UserService>()
val user = userService.getUser(userId)
requestLogger.log("Request completed")
metrics.end()
call.respond(user)
}
}
Request-scoped components can automatically inject ApplicationCall:
class RequestLogger(private val call: ApplicationCall) {
fun log(message: String) {
val requestPath = call.request.path()
val method = call.request.httpMethod.value
println("[$method $requestPath] $message")
}
}
class UserSessionHandler(private val call: ApplicationCall) {
fun getUserId(): String? {
return call.request.headers["X-User-ID"]
}
fun isAuthenticated(): Boolean {
return call.request.headers["Authorization"] != null
}
}
requestScope {
scoped { RequestContext(get()) }
// onCreate callback
onCreate { requestContext ->
requestContext.startTime = System.currentTimeMillis()
}
// onClose callback
onClose { requestContext ->
val duration = System.currentTimeMillis() - requestContext.startTime
println("Request completed in ${duration}ms")
}
}
:::note Request scopes are created and destroyed for each HTTP request. Instances are not shared between requests, ensuring thread safety and preventing state leakage. :::
Koin provides convenient functions to declare modules directly within your Ktor application.
Declare modules inline:
fun Application.configureRouting() {
koinModule {
singleOf(::CustomerRepository)
singleOf(::CustomerService)
}
routing {
customerRoutes()
}
}
Load multiple existing modules:
fun Application.configureCustomerFeature() {
koinModules(
customerRepositoryModule,
customerServiceModule,
customerRoutesModule
)
routing {
customerRoutes()
}
}
Organize your Ktor app by feature:
// Feature 1: Customer Management
fun Application.customerModule() {
koinModule {
singleOf(::CustomerRepository)
singleOf(::CustomerService)
}
routing {
route("/api/customers") {
customerRoutes()
}
}
}
// Feature 2: Order Management
fun Application.orderModule() {
koinModule {
singleOf(::OrderRepository)
singleOf(::OrderService)
}
routing {
route("/api/orders") {
orderRoutes()
}
}
}
// Main application
fun Application.module() {
install(Koin) {
slf4jLogger()
modules(coreModule)
}
customerModule()
orderModule()
}
Use annotations for request-scoped components:
@Scope(RequestScope::class)
class RequestLogger(private val call: ApplicationCall) {
fun log(message: String) {
println("[${call.request.path()}] $message")
}
}
val appModule = module {
singleOf(::UserRepositoryImpl) bind UserRepository::class
singleOf(::UserService)
requestScope {
scopedOf(::RequestLogger)
scopedOf(::RequestMetrics)
onCreate { metrics: RequestMetrics ->
metrics.start()
}
onClose { metrics: RequestMetrics ->
metrics.recordDuration()
}
}
}
fun Application.module() {
install(Koin) {
slf4jLogger()
modules(appModule)
}
routing {
get("/api/users/{id}") {
val logger = call.scope.get<RequestLogger>()
val userService = get<UserService>()
logger.log("Fetching user")
val user = userService.getUser(call.parameters["id"]!!)
call.respond(user)
}
}
}
| Function | Description |
|---|---|
requestScope { } | Declare request-scoped components |
call.scope.get<T>() | Get request-scoped instance |
onCreate { } | Callback when scope is created |
onClose { } | Callback when scope is closed |
koinModule { } | Declare inline module |
koinModules(...) | Load existing modules |