Step-by-step Full Stack Kotlin (Part 2)
In this three-part series, I will demonstrate how to create a simple full stack web app in Kotlin. The result is an app that allows a user to download and tag random inspirational images and then search for images by tag.
Part 2: Developing the backend database access layer with Exposed
Part 3: Developing the frontend with React and Kotlin/JS
As a reminder, let's have a look at the API we are building again:
1. To get a new inspiration:
2. To change the tags for an existing inspiration:
3. To get an existing inspiration image:
4. To find a tag by title:
5. To find an inspiration by tag
Exposed is another library by JetBrains for connecting to and manipulating relational databases using Kotlin and coroutines. The databases currently supported are PostgreSQL, MySQL, MariaDB, Oracle, H2 and SQL server. Exposed code can be written using a DSL or DAO style. For this project, I chose the DSL style. I used H2, an in-memory database because it didn’t need to be set up, and HikariCP for connection pooling.
Three tables need to be set up: one for saving inspirations (Inspirations), one for tags (Tags), another link table (InspirationTags) since there is a many-to-many relationship between inspirations and tags. The tables are defined in Exposed in the code snippet below.
Notice that the tables are declared as object, that is- they are singletons. Lines 14-15 illustrates how to create a foreign key dependency on primary keys of another table.
Hikari is a library that is going to help connect to the database and perform connection pooling. The code snippet below shows how to setup Hikari.
Since there’s no need to create the actual database, all that is required is to connect to the database using Hikari, and create the three tables we defined before. The code snippet below shows how to do this.
Before we start manipulating and querying data, it’s important to mention that Exposed works with coroutines. This makes sense as Input/Output (IO) isn’t always a quick operation. Therefore, we have to do the following things:
Mark each function with the suspend modifier
Ensure that the context with which the database code runs is within the IO context
The second point can easily be accomplished by wrapping the entire function implementation of database function in the function below, which will ensure that it is both in the IO context and in its own transaction.
The code snippet below illustrates how to create the image file and the inspiration entry in the database. Notice that it is marked with the suspend modifier because it might be a long-running operation. Futhermore, the code is wrapped in the dbQuery function to ensure the code block is running in a transaction and the right coroutine context.
Let's focus on the code in lines 12-16. The Inspirations table’s insert function along with lambda code block is used to insert an inspiration into the table. The it variable can be seen as a row of the Inspirations table, and each line of code in the block is setting the value of a cell in that row. In line 16, we are using an infix method get to retrieve from the insert operation’s result set the id of the new row, so that we can recover the new inspiration and return it to the client.
The code snippet below illustrates how to find inspirations for a particular user by userId. Notice once again it has been modified as a suspend function, and the code surrounded with the dbQuery function.
The Inspirations table’s select function and a conditional argument in the form of a lambda are used to model an SQL query. Notice how nicely the infix function eq works in the conditional lambda. It reads like natural language: the column value equals the userId.
The database model is then mapped to our domain model. This mapping has been omitted here but is available in the GitHub repository.
Ktor and Exposed make a great alternative to the more traditional Spring Boot and JPA. By taking a DSL-based approach, your code will be more concise and readable, while using coroutines will make your app performant.
This has been part two of a three-part series on creating a Full Stack Kotlin web app. Next week, we take a look at creating the frontend with React and Kotlin/JS.
With thanks to Annyce Davis for her reviews and suggestions.