Comparison between Spine and Ktor Resources¶
Ktor Resources are a first-party feature of Ktor that allows declaring endpoints as annotated classes.
Spine is a library that provides a DSL for declaring Ktor endpoints in code shared between client and server.
This page describes the differences in approach between these two solutions to help you choose the one you prefer.
Features¶
Declaring endpoints¶
Declare a simple endpoint¶
We define the existence of the path /articles in our API.
Declare a simple endpoint with a query parameter¶
We define the existence of the path /articles with the query parameter ?sort=new.
object Articles : RootResource("articles") {
class SearchParams(data: ParameterStorage) : Parameters(data) {
var sort: String? by parameter("new")
}
}
Because Spine doesn't use reflection nor compiler plugins, it requires slightly more configuration to declare query parameters.
Declare a simple endpoint with a query parameter and a method¶
Ktor Resources cannot encode the HTTP method as part of the resource. This means that all HTTP methods on a given resource must use the same query parameters.
object Articles : RootResource("articles") {
class SearchParams(data: ParameterStorage) : Parameters(data) {
var sort: String? by parameter("new")
}
val list by get() //(1)!
.parameters(::SearchParams)
val create by post() //(2)!
}
- Configure the behavior of the
GET /articlesendpoint, which has asortquery parameter. - Configure the behavior of the
POST /articlesendpoint, which has no particular query parameters.
Declare a nested endpoint¶
We define the existence of the path /articles/new.
@Resource("/articles")
class Articles {
@Resource("new")
class New(val parent: Articles = Articles())
}
Notice that the child resource must refer to the parent one. If the parent resource has mandatory query parameters, the child resource must specify them too.
object Articles : RootResource("articles") {
object New : StaticResource<Articles>("new", Articles) {
val post by post()
}
}
Note that we define which HTTP methods are allowed, which Ktor Resources cannot do.
Alternatively, we can avoid declaring a nested resource and simply declare the endpoint as-is:
Declare a nested endpoint with a path parameter¶
We define the existence of the path /articles/{id}.
@Resource("/articles")
class Articles {
@Resource("{id}")
class Id(val parent: Articles = Articles(), val id: Long)
}
Note that the name of the variable id must match the name of the path parameter.
object Articles : RootResource("articles") {
object Id : DynamicResource<Articles>("id", Articles)
}
The path parameter is automatically handled by the DynamicResource class. However, it will be typed as String.
Server-side¶
Implement an endpoint server-side¶
install(Resources)
routing {
get<Articles> { resource ->
println("Get all articles")
}
}
Implement an endpoint with query parameters server-side¶
install(Resources)
routing {
get<Articles> { resource ->
println("Get all articles, sorted by ${resource.sort}")
}
}
Implement an endpoint with path parameters server-side¶
install(Resources)
routing {
get<Articles.Id> { resource ->
println("Get the article ${resource.id}")
}
}
Client-side¶
Call an endpoint client-side¶
Call an endpoint with query parameters client-side¶
Call an endpoint with path parameters client-side¶
Conclusion¶
Here's a recap of features:
| Feature | Ktor Resources | Spine |
|---|---|---|
| Type-safe path | ✓ | ✓ |
| Type-safe path parameters | ✓ | Must be String |
| Type-safe query parameters | ✓ | ✓ |
| Different query parameters depending on the HTTP method | No | ✓ |
| Mandatory query parameters in non-leaf resources | No | ✓ |
| Declare the HTTP method along the resource | No | ✓ |
| Type-safe request body | No | ✓ |
| Type-safe response body | No | ✓ |
| Type-safe failures | No | ✓ |
| Access the path of an endpoint | ✓ | ✓ |
| List the parameters of an endpoint | With reflection | ✓ |
As a summary:
- Ktor Resources exist to avoid hard-coding endpoint paths. Instead, you can create a Resource class and reuse it in multiple places, between client and server.
- Spine exists to share most usual endpoint metadata, like the path and query parameters, but also request and response bodies, failure conditions, and more.