Type-safe Routing
Ktor provides a mechanism to create routes in a typed way, for both:constructing URLs and reading the parameters.
Locations are an experimental feature.
Table of contents:
- Installing the feature
- Defining route classes
- Defining route handlers
- Building URLs
- Subroutes with parameters
This feature is defined in the class io.ktor.locations.Locations
in the artifact io.ktor:ktor-locations:$ktor_version
.
dependencies { implementation "io.ktor:ktor-locations:$ktor_version"}
dependencies { implementation("io.ktor:ktor-locations:$ktor_version")}
<project> … <dependencies> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-locations</artifactId> <version>${ktor.version}</version> <scope>compile</scope> </dependency> </dependencies></project>
Installing the feature
The Locations feature doesn’t require any special configuration:
install(Locations)
Defining route classes
For each typed route you want to handle, you need to create a class (usually a data class)containing the parameters that you want to handle.
The parameters must be of any type supported by the Data Conversion feature.By default, you can use Int
, Long
, Float
, Double
, Boolean
, String
, enums and Iterable
as parameters.
URL parameters
That class must be annotated with @Location
specifyinga path to match with placeholders between curly brackets {
and }
. For example: {propertyName}
.The names between the curly braces must match the properties of the class.
@Location("/list/{name}/page/{page}")
data class Listing(val name: String, val page: Int)
- Will match:
/list/movies/page/10
- Will construct:
Listing(name = "movies", page = 10)
GET parameters
If you provide additional class properties that are not part of the path of the @Location
,those parameters will be obtained from the GET’s query string or POST parameters:
@Location("/list/{name}")
data class Listing(val name: String, val page: Int, val count: Int)
- Will match:
/list/movies?page=10&count=20
- Will construct:
Listing(name = "movies", page = 10, count = 20)
Defining route handlers
Once you have defined the classes annotated with @Location
,this feature artifact exposes new typed methods for defining route handlers:get
, options
, header
, post
, put
, delete
and patch
.
routing {
get<Listing> { listing ->
call.respondText("Listing ${listing.name}, page ${listing.page}")
}
}
Some of these generic methods with one type parameter, defined in the io.ktor.locations
, have the same name as other methods defined in the io.ktor.routing
package. If you import the routing package before the locations one, the IDE might suggest you generalize those methods instead of importing the right package. You can manually add import io.ktor.locations.*
if that happens to you.Remember this API is experimental. This issue is already reported at github.
Building URLs
You can construct URLs to your routes by calling application.locations.href
withan instance of a class annotated with @Location
:
val path = application.locations.href(Listing(name = "movies", page = 10, count = 20))
So for this class, path
would be "/list/movies?page=10&count=20""
.
@Location("/list/{name}") data class Listing(val name: String, val page: Int, val count: Int)
If you construct the URLs like this, and you decide to change the format of the URL,you will just have to update the @Location
path, which is really convenient.
Subroutes with parameters
You have to create classes referencing to another class annotated with @Location
like this, and register them normally:
routing {
get<Type.Edit> { typeEdit -> // /type/{name}/edit
// ...
}
get<Type.List> { typeList -> // /type/{name}/list/{page}
// ...
}
}
To obtain parameters defined in the superior locations, you just have to includethose property names in your classes for the internal routes. For example:
@Location("/type/{name}") data class Type(val name: String) {
@Location("/edit") data class Edit(val type: Type)
@Location("/list/{page}") data class List(val type: Type, val page: Int)
}