Skip to main content
Version: 4.2 - LTS

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​