Declare fullstack Ktor resources with Spine¶
With Spine, endpoints are grouped in resources. Each resource describes its path, while the endpoints within describe what the client can do.
Resources form a tree: each resource has a reference to its parent, up to the API root.
There are three types of resources:
RootResourceis the root of the API.StaticResourceis a typical hierarchy marker.DynamicResourcerepresents a path parameter.
In Kotlin, resources are typically described as nested classes, though that is not mandatory. Here is an example that describes the endpoints:
GET /v2/usersPOST /v2/usersGET /v2/users/123DELETE /v2/users/123
object V2 : RootResource("v2") {
object Users : StaticResource<V2>("users", V2) {
val list by get()
val create by post()
object User : DynamicResource<Users>("user", Users) {
val get by get()
val delete by delete()
}
}
}
- Server-side notation:
V2.Users.User.get - Client-side notation:
V2 / Users / User("123") / User.get
object V2 : RootResource("v2")
object Users : StaticResource<V2>("users", V2) {
val list by get()
val create by post()
}
object User : DynamicResource<Users>("user", Users) {
val get by get()
val delete by delete()
}
- Server-side notation:
V2.Users.User.get - Client-side notation:
V2 / Users / User("123") / User.get
The root resource¶
Reference:
RootResource
The root resource is the root of a specific API.
A single project could have multiple roots. This is typically used in versioned APIs:
Static resources¶
Reference:
StaticResource
A static resource represents a path within another resource. Each static resource must refer to its direct parent in the constructor call.
For example, if we want to declare the endpoints:
GET /v2/usersGET /v2/blogGET /v2/blog/archives
object Api2 : RootResource("v2") {
object Users : StaticResource<Api2>("users", Api2)
object Blog : StaticResouce<Api2>("blog", Api2) {
object Archives : StaticResource<Blog>("archives", Blog)
}
}
println(Api2.Blog.Archives.path) // /v2/users/blog/archives
To refer to a static resource on the server-side, you can use the regular Kotlin . notation: Api2.Blog.Archives.
To refer to a static resource on the client-side, you can use the slash notation with member imports: Api2 / Blog / Archives. Alternatively, without member imports: Api2 / Api2.Blog / Api2.Blog.Archives. This is necessary to correctly interpret dynamic resources.
Dynamic resources¶
Reference:
DynamicResource
Dynamic resources represent a segment that can have multiple different values at runtime. Typically, this is used for the ID of a resource.
For example, if we want to declare the endpoints:
GET /v2/usersGET /v2/users/{user}GET /v2/users/{user}/friendsGET /v2/users/{user}/friends/{friend}
object V2 : RootResource("v2") {
object Users : StaticResource<Api2>("users", Api2) {
object User : DynamicResource<Users>("user", Users) {
object Friends : StaticResource<User>("friends", User) {
object Friend : DynamicResource<Friends>("friend", Friends)
}
}
}
}
To refer to a dynamic resource on the server-side, you can use the regular Kotlin . notation: Api2.Users.User.Friends.Friend.
When implementing a dynamic resource on the server-side, you can access the value of the path parameter using the method idOf:
route(Api2.Users.User.Friends.Friend) {
val userId = idOf(Api2.Users.User)
val friendId = idOf(Api2.Users.User.Friends.Friend)
// …
}
When calling a dynamic resource on the client-side, you can use the slash notation with member imports: Api2 / Users / User("123") / Friends / Friend("456").
Resources describe the overall structure of the API. Within each resource, you can declare endpoints to describe the sent and received data.