Declare fullstack Ktor endpoints with Spine¶
Reference:
AnyEndpoint
Once you have described the resources of your API, you can declare endpoints. Each endpoint is a combination of a path, an HTTP method, and more optional information, like the body type.
Endpoints can only be declared within a resource. They are declared with the by keyword followed by the name of the HTTP method they correspond to.
The name of the variable itself doesn't matter.
For the remainder of this page, the enclosing resource is omitted from code samples for conciseness.
Do not explicitly write the type of your endpoints
In Kotlin, we have the choice of explicitly writing a variable's type, or letting the compiler infer it.
However, this should be avoided for Spine endpoints. We recommend letting the compiler infer the type. If you write the type explicitly, it is likely that your code will not compile anymore when new versions of Spine are released. If you're curious, you can learn more here.If you really must write the type of an endpoint (for example if you write an introspection function that prints all endpoints), you should use the `AnyEndpoint` type, which has access to all endpoint informatino but without type safety.
HTTP method¶
An endpoint is declared using by followed by the name of the HTTP method. The available methods are:
by get(): HTTPGETby post(): HTTPPOSTby put(): HTTPPUTby patch(): HTTPPATCHby delete(): HTTPDELETEby head(): HTTPHEAD
Specifying a path¶
Optionally, an endpoint can include an additional path segment. This is useful if you have one or two endpoints with a subpath and don't want to create a dedicated resource.
object Users : StaticResource("users") { //(1)!
val list by get() //(2)!
val listVips by get("vips") //(3)!
}
- The static resource
/users. - The
GET /usersendpoint. - The
GET /users/vipsendpoint.
is equivalent to:
object Users : StaticResource("users") { //(1)!
val list by get() //(2)!
object Vips : StaticResource("vips") { //(3)!
val list by get() //(4)!
}
}
- The static resource
/users. - The
GET /usersendpoint. - The static resource
/users/vips. - The
GET /users/vipsendpoint.
Request and response body¶
An endpoint can declare its request and response body.
The request and response body go through Ktor's usual content negotiation plugin.
For example, if you use KotlinX.Serialization, then the request and response bodies must be annotated with @Serializable.
Any other content negotiation library compatible with Ktor is also compatible with Spine.
val listUsers by get()
.response<List<UserDto>>() //(1)!
val deleteUser by delete()
.request<UserDeletionDto>() //(2)!
val createUser by post()
.request<UserCreationDto>()
.response<UserDto>()
requestallows declaring the type of the data sent by the client. The value will go through Ktor's usual content negotiation, following your existing configuration.responseallows declaring the type of the data sent by the server. The value will go through Ktor's usual content negotiation, following your existing configuration.
On the server-side, the request body is accessible through the body variable, and the response body can be sent with the function respond:
route(Users.createUser) {
val user = UserDto(newId(), name = body.name)
users.create(user)
respond(user)
}
On the client-side, the request body is passed as the second argument of the function request, and the response is acquired via the result of bodyOrThrow:
Query parameters¶
Query parameters can be declared with the parameters function:
To learn more, read the article on query parameters.
Failures¶
Spine is able to type-safely represent the different failure conditions of an endpoint:
To learn more, read the article on failures.