CRUD Rest #backend with Kotlin and Http4k

Cosmin Victor
4 min readMay 12, 2021

Overview

This guide aims to help you create from scratch a fast CRUD RESTful API with Kotlin and http4k in only 30 minutes. We’ll create a basic shopping list CRUD backend using http4k’s smart contract. The result will be a fast, simple REST application.

We’ll cover the following topics in this tutorial :

  • Building our Shopping list item data class.
  • Creating CRUD (Create, Read, Update, Delete) endpoints to manage our list.
  • Differences between GET, POST, PUT, and DELETE from HTTP request methods.
  • HttpHandlers.

What is http4k?

http4k’s purpose is very simple and well described on their official website:

http4k is a lightweight but fully-featured HTTP toolkit written in pure Kotlin that enables the serving and consuming of HTTP services in a functional and consistent way. http4k applications are just Kotlin functions that can be mounted into a running backend.

Some of the pros of http4k:

  • Small and independent library. The core library has no dependencies and it’s all you need to create fully functional applications.
  • Very flexible. Supports many Server, Serverless, and HTTP Client technologies behind simple, consistent interfaces.
  • Helpful Slack community. The authors and others users are very helpful on the slack channel, and updates keep coming very quickly.

The coding part 🚀

(Check this quick start guide for generating a project and adding the needed dependencies)

We will first start by creating a simple Kotling data class for a Shopping List item.

data class ShoppingListEntry(
val id: String? = null,
val itemName: String? = null,
val completed: Boolean? = false
)

These ShoppingListEntry items will then be managed in a simple mutableMap.

val shoppingList = mutableMapOf<String, ShoppingListEntry>()

After defining our list, we will then define two lenses for ShoppingList Items and the ShoppingList itself.

http4k lenses are bi-directional entities that can be used to either get or set a particular value from/onto an HTTP message. The corresponding API to describe lenses comes in the form of a DSL which also lets us define the requirement (optional vs. mandatory) of the HTTP part we are mounting a lens on. In simpler words, it means that for example, we can extract the JSON from an HTTP Post and, as long as the JSON has the same structure as our data class, we can use it to convert it to an object of type ShoppingListEntry.

val shoppingListItemBody = Body.auto<ShoppingListEntry>().toLens()
val shoppingListBody = Body.auto<List<ShoppingListEntry>>().toLens()

Next, we can move on to defining our first endpoint. An endpoint is basically defined as a function that returns a HttpHandler. We can start with the getShoppingList() handler that will return the whole shopping list.

fun getShoppingList(): HttpHandler = {
shoppingList.values.toList().let {
Response(OK).with(shoppingListBody of it)
}
}

As we can see above we convert our map to a list then map the list to a JSON response via the bi-directional lens. That JSON will form the body of the response. Response(OK) will return a HTTP 200 response.

Going forward we can see how we can handle an HTTP Post request and extract the body into a data class object. After the body is mapped to an object of type ShoppingListEntry, the object will then be added to our list. The endpoint will return the updated ShoppingList.

fun addShoppingListItem(): HttpHandler = {
val newItem = shoppingListItemBody.extract(it)
shoppingList.put(newItem.id!!, newItem)
shoppingList.values.toList().let {
Response(OK).with(shoppingListBody of it)
}
}

After defying all of our handlers (full code with all handlers is at the end of the post) needed to manage our Shopping List, we will then map them to specific endpoints.

val globalFilters = DebuggingFilters.PrintRequestAndResponse()globalFilters.then(
contract(
Path.of("id") bindContract GET to ::getShoppingListItem,
Path.of("id") bindContract PATCH to ::changeShoppingListItem,
Path.of("id") bindContract DELETE to ::deleteShoppingListItem,
"/" bindContract GET to getShoppingList(),
"/" bindContract POST to addShoppingListItem(),
"/" bindContract DELETE to clearShoppingList()
)
).asServer(Jetty(8005)).start()

The first line has the purpose of defining a filter that will print the request and the response, this is usually helpful for debugging.

After the filter is injected we proceed with mapping endpoints to handlers. For example, let’s look at the first endpoint. Path.of(“id”) could be translated to “/{id}”. Id in this case is the id used to retrieve a specific ShoppingListItem. This endpoint is then mapped to our getShoppingListItem handler.

After the endpoints are mapped we run the RoutingHttpHandlers as a server. We choose Jetty for our application but you can use any of the supported servers. 8005 is the port we will start our server on.

And this is how in 30 minutes we can build a fast and simple http4k and kotlin based REST backend.

Here is my LinkedIn in case you want to get in touch.

Thanks for reading !! Feel free to leave any comments.

And here is the full source code (The editor will break a bit the formatting).

fun main(args: Array<String>) {

val shoppingList = mutableMapOf<String, ShoppingListEntry>()
val shoppingListItemBody = Body.auto<ShoppingListEntry>().toLens()
val shoppingListBody = Body.auto<List<ShoppingListEntry>>().toLens()

fun getShoppingListItem(id: String): HttpHandler = {
shoppingList[id]?.let { Response(OK).with(shoppingListItemBody of it) } ?: Response(NOT_FOUND)
}

fun changeShoppingListItem(id: String): HttpHandler = {
val newItem = shoppingListItemBody.extract(it)
val patched = shoppingList[id]?.patch(newItem)
shoppingList.replace(id, patched!!)
shoppingList[id]?.let { Response(OK).with(shoppingListItemBody of it) } ?: Response(NOT_FOUND)
}

fun deleteShoppingListItem(id: String): HttpHandler = {
shoppingList.remove(id)?.let { Response(OK).with(shoppingListItemBody of it) } ?: Response(NOT_FOUND)
}

fun getShoppingList(): HttpHandler = {
shoppingList.values.toList().let {
Response(OK).with(shoppingListBody of it)
}
}

fun clearShoppingList(): HttpHandler = {
shoppingList.clear()
shoppingList.values.toList().let { Response(OK).with(shoppingListBody of it) }
}

fun addShoppingListItem(): HttpHandler = {
val newItem = shoppingListItemBody.extract(it)
shoppingList[newItem.id!!] = newItem
shoppingList.values.toList().let {
Response(OK).with(shoppingListBody of it)
}
}

//Filter for debbuging
val globalFilters = DebuggingFilters.PrintRequestAndResponse()

//mapping endpint to handlers
globalFilters.then(
contract(
Path.of("id") bindContract GET to ::getShoppingListItem,
Path.of("id") bindContract PATCH to ::changeShoppingListItem,
Path.of("id") bindContract DELETE to ::deleteShoppingListItem,
"/" bindContract GET to getShoppingList(),
"/" bindContract POST to addShoppingListItem(),
"/" bindContract DELETE to clearShoppingList()
)
).asServer(Jetty(8005)).start()
}


data class ShoppingListEntry(
val id: String? = null,
val itemName: String? = null,
val completed: Boolean? = false
)

// extension function for patching a Shopping List item.
private fun ShoppingListEntry.patch(toUpdate: ShoppingListEntry): ShoppingListEntry =
copy(id = toUpdate.id ?: id, completed = toUpdate.completed ?: completed, itemName = toUpdate.itemName ?: itemName)

--

--