Share your Ktor endpoints between client and server spine.opensavvy.dev
rest http ktor kotlin arrow
README.md

Module Multiplatform Ktor schema declaration #

Describe your Ktor API in code shared between the client and the server.

When creating fullstack projects, using both Ktor as a client and a server, we need to make sure we are calling the same endpoints on both sides, with the same expected DTOs, expect the same failures, etc.

Using Kotlin Multiplatform and KotlinX.Serialization, we can easily share DTOs—but the structure of the API isn't as easy to share.

Declaring a schema #

Spine is a library to declare a schema of our API in pure Kotlin. Once it is declared, we can use it identically on the client and server sides.

First, declare a dependency on dev.opensavvy.spine:api.

We define that:

  • a resource is an imaginary data collection,
  • an endpoint is a single operation that acts on a given resource.

We can declare the structure of our API like this:

// Declare our root endpoint: /v1
object Api : RootResource("v1") {
	
	// Declare a nested resource: /v1/users
	object Users : StaticResource<Api>("/users", parent = Api) {
		
		// GET /v1/users
		// which returns a list of UserDto
		val list by get()
			.response<List<UserDto>>()
		
		// Declare a nested resource: /v1/users/{user}
		object User : DynamicResource<Users>("user", parent = Users) {
			
			// GET /v1/users/{user}
			// which returns a UserDto
			val get by get()
				.response<UserDto>()
			
			// POST /v1/users/{user}
			// which accepts a UserCreationDto and returns a UserDto
			val create by post()
				.request<UserCreationDto>()
				.response<UserDto>()
			
			// PUT /v1/users/{user}/friend
			val addFriend by put("friend")

			// DELETE /v1/users/{user}/friend
			val removeFriend by delete("friend")
		}
	}
}

We can then refer to any endpoint easily. For example, Api.Users.User.removeFriend is the DELETE /v1/users/{user}/friend endpoint.

Learn more #

Resources describe a grouping of endpoints under a single URL:

  • [RootResource][opensavvy.spine.api.RootResource] is the root of a URL.
  • [StaticResource][opensavvy.spine.api.StaticResource] is a hard-coded segment in a URL, for example /users or /posts.
  • [DynamicResource][opensavvy.spine.api.DynamicResource] is a wildcard segment in a URL, which could be replaced by a user's ID, for example.

Endpoints describe a specific HTTP method along with its expected input and output types, parameters, etc.

  • [AnyEndpoint][opensavvy.spine.api.AnyEndpoint] allows introspecting information about an endpoint.
  • [AnyEndpoint.Builder][opensavvy.spine.api.AnyEndpoint.Builder] allows declaring information about an endpoint.
  • [Parameters][opensavvy.spine.api.Parameters] represent query parameters.

To learn how to use the APIs on the client or on the server, see the documentation of the client and server modules.