Skip to main content
Version: 4.2

Request Scopes in Ktor

Request scopes create instances that live for the duration of a single HTTP request, perfect for request-specific data and processing.

Declaring Request-Scoped Components

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)
}
}

Accessing Request-Scoped Components

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)
}
}

Injecting ApplicationCall

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
}
}

Scope Lifecycle Callbacks

Attach an onClose callback to a scoped definition to run cleanup when the request scope is closed. onClose is an infix function on the definition (not a block inside requestScope { }), and the instance parameter is nullable (T?):

requestScope {
scoped { RequestContext() } onClose { context ->
val duration = System.currentTimeMillis() - (context?.startTime ?: 0L)
println("Request completed in ${duration}ms")
}
}

You can also use withOptions { onClose { ... } }:

requestScope {
scoped { RequestContext() } withOptions {
onClose { context ->
context?.cleanup()
}
}
}
note

Koin's scope DSL exposes onClose per definition; there is no onCreate callback. Initialization should happen in the scoped { } constructor block.

note

Request scopes are created and destroyed for each HTTP request. Instances are not shared between requests, ensuring thread safety and preventing state leakage.

Declaring Modules in Ktor

Koin provides convenient functions to declare modules directly within your Ktor application.

Using koinModule

Declare modules inline:

fun Application.configureRouting() {
koinModule {
singleOf(::CustomerRepository)
singleOf(::CustomerService)
}

routing {
customerRoutes()
}
}

Using koinModules

Load multiple existing modules:

fun Application.configureCustomerFeature() {
koinModules(
customerRepositoryModule,
customerServiceModule,
customerRoutesModule
)

routing {
customerRoutes()
}
}

Modular Application Structure

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()
}

Request Scope with Annotations

Use annotations for request-scoped components:

@Scope(RequestScope::class)
class RequestLogger(private val call: ApplicationCall) {
fun log(message: String) {
println("[${call.request.path()}] $message")
}
}

Complete Example

val appModule = module {
singleOf(::UserRepositoryImpl) bind UserRepository::class
singleOf(::UserService)

requestScope {
scopedOf(::RequestLogger)
scopedOf(::RequestMetrics) onClose { metrics ->
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)
}
}
}

API Reference

FunctionDescription
requestScope { }Declare request-scoped components
call.scope.get<T>()Get request-scoped instance
scoped { } onClose { }Per-definition cleanup callback (instance is nullable)
koinModule { }Declare inline module
koinModules(...)Load existing modules

See Also