<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ktor Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/category/ktor/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.codersee.com/category/ktor/</link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Thu, 17 Apr 2025 09:45:17 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>Ktor Archives - Codersee blog- Kotlin on the backend</title>
	<link>https://blog.codersee.com/category/ktor/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>A Quick Guide to htmx in Kotlin</title>
		<link>https://blog.codersee.com/quick-quide-to-htmx-kotlin/</link>
					<comments>https://blog.codersee.com/quick-quide-to-htmx-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sat, 29 Mar 2025 17:25:53 +0000</pubDate>
				<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[Ktor]]></category>
		<category><![CDATA[htmx]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=18012838</guid>

					<description><![CDATA[<p>In this tutorial, we will learn the most important htmx concepts and create a basic application with Kotlin HTML DSL and Tailwind CSS.</p>
<p>The post <a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/">A Quick Guide to htmx in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>To be more specific, we will see a step-by-step process of writing <strong>HTML </strong>with typesafe <strong>Kotlin DSL</strong>, integrating <strong>htmx</strong>, and handling requests with <strong>Ktor</strong>. </p>



<p>Eventually, we will get the below user table:</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="521" src="http://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-1024x521.webp" alt="Image presents a screenshot of result application created in Kotlin and htmx." class="wp-image-18012839" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-1024x521.webp 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-300x153.webp 300w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen-768x391.webp 768w, https://blog.codersee.com/wp-content/uploads/2025/03/htmx_kotlin_app_screen.webp 1113w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>But before we start, just a short disclaimer: although the main focus for this tutorial is the htmx / Kotlin / Ktor combination, I decided to bring <strong>Tailwind CSS</strong> to the project with the help of <a href="https://www.material-tailwind.com/" target="_blank" rel="noreferrer noopener nofollow">Material Tailwind components</a>. This way, I wanted to showcase the code that is closer to real-life scenarios. Not a next, plain HTML example. </p>



<p>So, although I tried my best, please keep in mind that the HTML part may need some more love when adapting 🫡</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: if you enjoy this content and would like to learn Ktor step-by-step, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>.</p>
</blockquote>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FtstB08EDClw%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-what-is-htmx">What is htmx? </h2>



<p>If you are here, then there is a high chance you&#8217;ve been looking for a Kotlin &amp; htmx combination, so you already know what it is. </p>



<p>Nevertheless, so we are all on on the same page: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>It is a library that allows you to access modern browser features directly from HTML, rather than using javascript.</p>
</blockquote>



<p>And we could add plenty of other things here, like the fact that it is small, dependency-free, and allows us to use AJAX, CSS Transitions, WebSockets, and so on. </p>



<p>But, IMO, the most important thing from the practical standpoint is that we can use attributes in HTML, and the library will do the &#8220;magic&#8221; for us:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;td>
  &lt;button hx-delete="/users/7a9079f0-c5a2-45d0-b4ae-e304b6908787" hx-swap="outerHTML" hx-target="closest tr">
... the rest
   </pre>



<p>The above following snippet means that when we click the button:</p>



<ul class="wp-block-list">
<li>the <code>DELETE /users/7a9079f0-c5a2-45d0-b4ae-e304b6908787</code> request is made</li>



<li>the closest tr element is replaced with the HTML response we receive</li>
</ul>



<p>And I believe this is all we need to know for now. </p>



<p>Again, a short note from my end: the <a href="https://htmx.org/docs/" target="_blank" rel="noreferrer noopener">htmx documentation</a> is a great resource, and I will refer a lot to it throughout this course to not reinvent the wheel. But at the same time, I want to deliver you a fully-contained article so that you don&#8217;t need to jump between pages 😉</p>



<h2 class="wp-block-heading" id="h-generate-ktor-project">Generate Ktor Project</h2>



<p>As the first step, let&#8217;s quickly generate a new Ktor project using <a href="https://start.ktor.io/">https://start.ktor.io/</a>:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="590" src="http://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-1024x590.png" alt="" class="wp-image-18012840" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-1024x590.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-300x173.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl-768x443.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_htmx_kotlin_html_dsl.png 1277w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, the only plugin we need to select is the <strong>Kotlin HTML DSL </strong>(this way, the Routing plugin is added, too).</p>



<p>Regarding the config, we are going to use <strong>Ktor 3.1.1</strong> with <strong>Netty</strong>, <strong>YAML </strong>config, and <strong>without a version catalog</strong>. But, of course, feel free to adjust it here according to your needs.</p>



<p>With that done, let&#8217;s download the project and import it to our IDE. </p>



<h2 class="wp-block-heading" id="h-create-user-repository">Create User Repository</h2>



<p>Following, let&#8217;s add the <code>repository</code> package and introduce a simple, in-memory user repository:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class User(
    val id: String = UUID.randomUUID().toString(),
    val firstName: String,
    val lastName: String,
    val enabled: Boolean,
    val createdAt: LocalDateTime = LocalDateTime.now(),
)
class UserRepository {
    private val users = mutableListOf(
        User(firstName = "Jane", lastName = "Doe", enabled = true),
        User(firstName = "John", lastName = "Smith", enabled = true),
        User(firstName = "Alice", lastName = "Johnson", enabled = false),
        User(firstName = "Bob", lastName = "Williams", enabled = true),
    )
    fun create(firstName: String, lastName: String, enabled: Boolean): User =
        User(firstName = firstName, lastName = lastName, enabled = enabled)
            .also(users::add)
    fun findAll(): List&lt;User> = users
    fun delete(id: String): Boolean = users.removeIf { it.id == id }
}</pre>



<p>As we can see, nothing spectacular. Just 3 functions responsible for creating, searching, and deleting users. </p>



<h2 class="wp-block-heading" id="h-return-html-response-in-ktor">Return HTML Response in Ktor</h2>



<p>Following, let&#8217;s add the <code>routing</code> package and <code>Routing.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureRouting(userRepository: UserRepository) {
    routing {
        get("/") {
            call.respondHtml {
                renderIndex(userRepository)
            }
        }
    }
}</pre>



<p>And update the main <code>Application.kt</code> to incorporate those changes: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.module() {
    val userRepository = UserRepository()
    configureRouting(userRepository)
}</pre>



<p>In a moment, we will add the <code>renderIndex</code> function, but for now, let&#8217;s focus on the above. </p>



<p>Long story short, the above code instructs the Ktor server to respond with the HTML response whenever it reaches the root path. By default, the <code>localhost:8080</code>. </p>



<h2 class="wp-block-heading" id="h-generate-html-page-with-kotlin-dsl">Generate HTML page with Kotlin DSL</h2>



<p>With that done, we have everything we need to start returning HTML responses. So in this section, we will prepare the baseline for htmx. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: if you feel that continuous server restarting is painful, please check out <a href="https://blog.codersee.com/ktor-auto-reload/">how to enable auto-reload in Ktor?</a>  </p>
</blockquote>



<h3 class="wp-block-heading" id="h-render-homepage">Render Homepage</h3>



<p>As the first step, let&#8217;s add the <code>html</code> package and the <code>renderIndex</code> function in <code>Index.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.repository.UserRepository
import kotlinx.html.*
fun HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
        }
    }
}
private fun FlowContent.insertHeader() {
    h5 {
        +"Users list"
    }
}</pre>



<p>At this point, such a structure is overengineering. Nevertheless, our codebase is about to grow quickly in this tutorial, so we rely on Kotlin extension functions from the very beginning. </p>



<p>And as we can see, we use the HTML that represents the root element of an HTML document. Inside it, we can define the structure in a type-safe manner, thanks to the <a href="https://blog.codersee.com/kotlin-type-safe-builders-make-your-custom-dsl/">Kotlin DSL</a> feature. For the inner tags, we can use the <code>FlowContent</code> that is the marker interface for plenty of classes representing HTML tags, like div, headers, or the body.</p>



<p>And before we rerun the application, we must add the necessary import in <code>Routing.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.repository.html.renderIndex</pre>



<p>With that done, let&#8217;s rerun the application and verify that everything is working. </p>



<h3 class="wp-block-heading" id="h-insert-html-form">Insert HTML Form</h3>



<p>Following, let&#8217;s use the HTML DSL to define the user form. </p>



<p>As the first step, let&#8217;s add the <code>Form.kt</code> to inside the <code>html</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.html.*
fun FlowContent.insertUserForm() {
    div {
        form {
            div {
                div {
                    label {
                        htmlFor = "first-name"
                        +"First Name"
                    }
                    input {
                        type = InputType.text
                        name = "first-name"
                        id = "first-name"
                        placeholder = "First Name"
                    }
                }
                div {
                    label {
                        htmlFor = "last-name"
                        +"Last Name"
                    }
                    input {
                        type = InputType.text
                        name = "last-name"
                        id = "last-name"
                        placeholder = "Last Name"
                    }
                }
                div {
                    label {
                        +"Account enabled"
                    }
                    div {
                        div {
                            input {
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-1"
                                value = "true"
                            }
                            label {
                                htmlFor = "radio-button-1"
                                +"Yes"
                            }
                        }
                        div {
                            input {
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-2"
                                value = "false"
                                checked = true
                            }
                            label {
                                htmlFor = "radio-button-2"
                                +"No"
                            }
                        }
                    }
                }
                div {
                    button {
                        +"Add user"
                    }
                }
            }
        }
    }
}</pre>



<p>As we can see, the <strong>Kotlin DSL allows us to define everything in a neat, structured manner</strong>. And although I had a chance to use it in various places, with HTML, it feels so&#8230;natural. We write the code pretty similar to HTML.</p>



<p>Of course, before heading to the next part, let&#8217;s make use of our function: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
            insertUserForm()
        }
    }
}</pre>



<p>We can clearly see that the extraction was a good idea 😉 </p>



<h3 class="wp-block-heading" id="h-create-user-table">Create User Table</h3>



<p>As the next step, let&#8217;s add the <code>UserTable.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.repository.User
import kotlinx.html.*
import java.time.format.DateTimeFormatter
fun FlowContent.insertUserTable(users: List&lt;User>) {
    div {
        table {
            thead {
                tr {
                    th {
                        +"User"
                    }
                    th {
                        +"Status"
                    }
                    th {
                        +"Created At"
                    }
                    th {}
                }
            }
            tbody {
                id = "users-table"
                users.forEach { user ->
                    tr {
                        insertUserRowCells(user)
                    }
                }
            }
        }
    }
}
fun TR.insertUserRowCells(user: User) {
    td {
        div {
            p {
                +"${user.firstName} ${user.lastName}"
            }
        }
    }
    td {
        div {
            div {
                val enabledLabel = if (user.enabled) "Enabled" else "Disabled"
                val labelColor = if (user.enabled) "green" else "red"
                span { +enabledLabel }
            }
        }
    }
    td {
        p {
            +user.createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        }
    }
    td {
        button {
            + "Delete"
        }
    }
}</pre>



<p>As we can see this time, the Kotlin HTML DSL allows us to easily generate <strong>dynamic HTML tags</strong>. We use the passed user list to create a row for every user we &#8220;persisted&#8221;. We even make the decision about the label and future color based on the list. </p>



<p>Again, let&#8217;s get back to <code>Routing.kt</code> and invoke our function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
    }
}
</pre>



<p>And although I am pretty sure it won&#8217;t become the eighth wonder of the World, our application starts looking similar to what we want to achieve:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="382" height="428" src="http://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen.png" alt="Image is a screenshot of a plain HTML application generated with Kotlin HTML DSL in Ktor" class="wp-image-18512857" style="object-fit:cover" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen.png 382w, https://blog.codersee.com/wp-content/uploads/2025/03/ktor_kotlin_html_dsl_plain_application_screen-268x300.png 268w" sizes="(max-width: 382px) 100vw, 382px" /></figure>



<h2 class="wp-block-heading" id="h-htmx-and-kotlin">htmx and Kotlin</h2>



<p>With all of that done, we have everything prepared to start actually working with <strong>htmx and Kotlin</strong>.</p>



<p>Again, you will see a lot of references taken from the <a href="https://htmx.org/docs/">docs</a> (which I encourage you to visit after this tutorial).</p>



<h3 class="wp-block-heading" id="h-import"> Import</h3>



<p>As the first step, let&#8217;s import <strong>htmx</strong>. </p>



<p>Again, it is nothing else than the JavaScript library, so the only thing we need is to add it inside the script block of our HTML. </p>



<p>And the easiest way is to simply fetch it from their CDN and put it inside the Kotlin <code>script</code> DSL block:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
    }
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
    }
}</pre>



<h3 class="wp-block-heading" id="h-ajax-requests-create-user">AJAX Requests &#8211; Create User</h3>



<p>As we saw in the very beginning, htmx allows us to define requests with attributes. </p>



<p>To be more specific, we can use the following attributes: </p>



<ul class="wp-block-list">
<li>hx-get</li>



<li>hx-post</li>



<li>hx-put</li>



<li>hx-patch</li>



<li>hx-delete</li>
</ul>



<p>And, long story short, when the element is triggered, an AJAX request is made to the specified URL.</p>



<p>So, let&#8217;s update our form then: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
... the rest</pre>



<p>This way, our html now contains:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;form hx-post="/users"></pre>



<p>And when we hit the <code>Add user</code> button, we can see that the request is triggered (but, it results in <code>404</code> response given we have no handler). </p>



<p>So, let&#8217;s add the endpoint responsible for user creation: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureRouting(userRepository: UserRepository) {
    routing {
        get("/") {
            call.respondHtml {
                renderIndex(userRepository)
            }
        }
        route("/users") {
            post {
                val formParams = call.receiveParameters()
                val firstName = formParams["first-name"]!!
                val lastName = formParams["last-name"]!!
                val enabled = formParams["enabled-radio"]!!.toBoolean()
                val createdItem = userRepository.create(firstName, lastName, enabled)
                val todoItemHtml = createHTML().tr { insertUserRowCells(createdItem) }
                call.respondText(
                    todoItemHtml,
                    contentType = ContentType.Text.Html,
                )
            }
        }
    }
}</pre>



<p>At this point, it should not be a surprise, but we can see that in Ktor, we can do that quite easily. </p>



<p>Our code snippet will read the form parameters sent from the browser, &#8220;create&#8221; a new user, and return a <code>200 OK</code> response with:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="html" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">&lt;tr>
  &lt;td>
    &lt;div>
      &lt;p>Admiral Jahas&lt;/p>
    &lt;/div>
  &lt;/td>
  &lt;td>
    &lt;div>
      &lt;div>&lt;span>Disabled&lt;/span>&lt;/div>
    &lt;/div>
  &lt;/td>
  &lt;td>
    &lt;p>2025-03-29 08:16:40&lt;/p>
  &lt;/td>
  &lt;td>&lt;button>Delete&lt;/button>&lt;/td>
&lt;/tr>
</pre>



<p>The <strong>important </strong>thing to mention here is that <code>respondHtml</code> requires us to respond with whole body! So, to bypass that, we use the <code>respondText</code> function and set the content type as HTML. </p>



<p>However, when we open up the browser, we can see this:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="388" height="362" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-3.png" alt="Image is a screenshot from htmx POST invocation with invalid target." class="wp-image-18512868" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-3.png 388w, https://blog.codersee.com/wp-content/uploads/2025/03/image-3-300x280.png 300w" sizes="auto, (max-width: 388px) 100vw, 388px" /></figure>



<p>And I am pretty sure that is not what we wanted 😀 </p>



<h3 class="wp-block-heading" id="h-htmx-target">htmx Target</h3>



<p><strong>Lesson one:</strong> if we want to instruct htmx to load the response into a different element than the one that made the request, we must use the <code>hx-target</code> attribute that takes the CSS selector, or:</p>



<ul class="wp-block-list">
<li><code>this</code> keyword- to refer to the element with <code>hx-target</code> attribute</li>



<li><code>closest</code>, <code>next</code>, <code>previous</code> <code>&lt;CSS selector&gt;</code> (like <code>closest div</code>)- to target the closest ancestor element or itself</li>



<li><code>find &lt;CSS selector</code> &#8211; to target the first child descendant element that matches the given CSS selector</li>
</ul>



<p>As a proof, let&#8217;s take a look at what happened previously: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="345" height="137" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-4.png" alt="" class="wp-image-18512871" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-4.png 345w, https://blog.codersee.com/wp-content/uploads/2025/03/image-4-300x119.png 300w" sizes="auto, (max-width: 345px) 100vw, 345px" /></figure>



<p>As we can see, the table row was inserted inside the <code>form</code>. And that does not make sense, at all. </p>



<p>So, to fix that, let&#8217;s target the <code>table tbody</code> instead: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"</pre>



<p>As a result, all the other rows are deleted, but it seems to be closer to what we want:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="379" height="249" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-5.png" alt="" class="wp-image-18512872" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-5.png 379w, https://blog.codersee.com/wp-content/uploads/2025/03/image-5-300x197.png 300w" sizes="auto, (max-width: 379px) 100vw, 379px" /></figure>



<h3 class="wp-block-heading" id="h-swapping-in-htmx">Swapping in htmx</h3>



<p><strong>Next lesson:</strong> by default, <strong>htmx replaces the innerHTML of the target element</strong>. </p>



<p>So, in our case, the user was added successfully. We can even refresh the page and see that the array contains all created users. However, we have not defined the <code>hx-swap</code> so the <code>tbody</code> inner HTML was deleted, and our returned one was inserted instead. </p>



<p>So, we must add the <code>hx-swap</code> with one of the following values:</p>



<ul class="wp-block-list">
<li><code>innerHTML</code>&#8211; puts the content inside the target element</li>



<li><code>outerHTML</code>&#8211; replaces the entire target element with the returned content</li>



<li><code>afterbegin</code>&#8211; prepends the content before the first child inside the target</li>



<li><code>beforebegin</code>&#8211; prepends the content before the target in the target’s parent element</li>



<li><code>beforeend</code>&#8211; appends the content after the last child inside the target</li>



<li><code>afterend</code>&#8211; appends the content after the target in the target’s parent element</li>



<li><code>delete</code>&#8211; deletes the target element regardless of the response</li>



<li><code>none</code>&#8211; does not append content from response (Out of Band Swaps and Response Headers will still be processed)</li>
</ul>



<p>And in our case, the <code>beforeend</code> is the one we should pick to append the created user at the end of the list:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun FlowContent.insertUserForm() {
    div {
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"
            attributes["hx-swap"] = "beforeend"</pre>



<p>When we restart the app, everything works fine! 🙂 </p>



<h3 class="wp-block-heading" id="h-dynamic-htmx-tags-in-kotlin">Dynamic htmx Tags in Kotlin</h3>



<p>At this point, we know how to display and add new users with htmx. So, let&#8217;s learn how to delete them.</p>



<p>As the first step, let&#8217;s prepare a Ktor handler inside the <code>route("/users")</code> for the DELETE request:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">delete("/{id}") {
    val id = call.parameters["id"]!!
    userRepository.delete(id)
    call.respond(HttpStatusCode.OK)
}</pre>



<p>With that code, whenever a <code>DELETE /users/{some-id}</code> is made, we remove the user from our list and return 200 OK.</p>



<p><strong>Important lesson here:</strong> for simplicity, we return <code>200 OK</code> (and not <code>204 No Content</code>), because by default, htmx ignores successful responses other than 200.</p>



<p>Following, let&#8217;s update our button: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">td {
        button {
            attributes["hx-delete"] = "/users/${user.id}"
            attributes["hx-swap"] = "outerHTML"
            attributes["hx-target"] = "closest tr"</pre>



<p>So, firstly, whenever we generate our button, we use <strong>Kotlin string interpolation</strong> to put the user identifier in the <code>hx-delete</code> attribute value. A neat and easy way to achieve that with Kotlin. </p>



<p>When it comes to swapping, we want to find the closest <code>tr</code> parent and swap the entire element with the response. And as the response contains <strong>nothing</strong>, it will be simply removed😉</p>



<p>After we rerun the application, we will see everything working perfectly fine!</p>



<h3 class="wp-block-heading" id="h-error-handling-with-ktor-and-htmx">Error Handling with Ktor and htmx</h3>



<p>Following, let&#8217;s learn how we can handle any Ktor error response in htmx. </p>



<p>For that purpose, let&#8217;s update the POST handler in Ktor:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">post {
    val formParams = call.receiveParameters()
    val firstName = formParams["first-name"]!!
    val lastName = formParams["last-name"]!!
    val enabled = formParams["enabled-radio"]!!.toBoolean()
    if (firstName.isBlank() || lastName.isBlank())
        return@post call.respond(HttpStatusCode.BadRequest)
... the rest of the code</pre>



<p>With that validation, whenever <code>first-name</code> or <code>last-name</code> form parameter is blank, the API client receives <code>400 Bad Request</code>.</p>



<p>After we restart the server and try to make a request without passing first or last name, we see that nothing is happening. No pop-ups, alerts, nothing. The only indication that the request is actually made is thenetwork tab of our browser.</p>



<p>Well, unfortunately (or fortunately?), htmx does not provide any handling out-of-the-box.</p>



<p>But, it throws two events:</p>



<ul class="wp-block-list">
<li><code>htmx:responseError</code>&#8211; in the event of an error response from the server, like <code>400 Bad Request</code></li>



<li><code>htmx:sendError</code>&#8211; in case of connection error</li>
</ul>



<p>So, let&#8217;s add a tiny bit of JS in Kotlin, then: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private fun BODY.insertErrorHandlingScripts() {
    script {
        +"""
            document.body.addEventListener('htmx:responseError', function(evt) {
              alert('An error occurred! HTTP status:' + evt.detail.xhr.status);
            });
            
            document.body.addEventListener('htmx:sendError', function(evt) {
              alert('Server unavailable!');
            });
        """.trimIndent()
    }
}</pre>



<p>And let&#8217;s add this script at the end of the body when rendering the homepage:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
    }
    body {
        div {
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
        insertErrorHandlingScripts()
    }
}</pre>



<p>Excellent! From now on, whenever the API client receives an error response, the alert is displayed. Moreover, if we turn off our server, we will see the error response, too.</p>



<p>And basically, that is all for the htmx part with Kotlin. From now on, we are going to work on the styling of our application😉</p>



<h2 class="wp-block-heading" id="h-returning-images-in-ktor">Returning Images in Ktor</h2>



<p>Before we head to the Tailwind CSS part, let&#8217;s learn one more thing in Ktor: static responses handling.</p>



<p>So, let&#8217;s put the below image in the <code>resources -&gt; img</code> directory:</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="500" height="500" src="http://blog.codersee.com/wp-content/uploads/2025/03/placeholder.png" alt="Image is a placeholder used ad an example in our project and shows Codersee logo." class="wp-image-18512878" style="width:100px" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/placeholder.png 500w, https://blog.codersee.com/wp-content/uploads/2025/03/placeholder-300x300.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/placeholder-150x150.png 150w" sizes="auto, (max-width: 500px) 100vw, 500px" /></figure>



<p>And let&#8217;s add this image as a placeholder to each row in our table: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun TR.insertUserRowCells(user: User) {
    td {
        div {
            img {
                src = "/img/placeholder.png"
            }</pre>



<p>When we rerun the application, we can see that it does not work.</p>



<p>Well, to fix that, we must instruct Ktor to serve our resources as static content:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureRouting(userRepository: UserRepository) {
    routing {
        staticResources("/img", "img")</pre>



<p>This time, when we restart the application, we see that placeholders are working fine. </p>



<p>And we will style them in a moment 😉 </p>



<h2 class="wp-block-heading" id="h-styling-with-tailwind-css">Styling With Tailwind CSS</h2>



<p>At this point, we have a fully working Kotlin and htmx integration.</p>



<p>So, if we already did something else than the JSON response, let&#8217;s make it nice😄</p>



<h3 class="wp-block-heading" id="h-import-tailwind">Import Tailwind</h3>



<p>Just like with htmx, let&#8217;s use the CDN to import Tailwind to the project:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
        script {
            src = "https://unpkg.com/@tailwindcss/browser@4"
        }
    }</pre>



<h3 class="wp-block-heading" id="h-update-index">Update Index </h3>



<p>Then, let&#8217;s navigate to the <code>Index.kt</code> and add adjustments:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun HTML.renderIndex(userRepository: UserRepository) {
    head {
        script {
            src = "https://unpkg.com/htmx.org@2.0.4"
        }
        script {
            src = "https://unpkg.com/@tailwindcss/browser@4"
        }
    }
    body {
        div {
            classes = setOf("m-auto max-w-5xl w-full overflow-hidden")
            insertHeader()
            insertUserForm()
            insertUserTable(userRepository.findAll())
        }
        insertErrorHandlingScripts()
    }
}
private fun FlowContent.insertHeader() {
    h5 {
        classes =
            setOf("py-8 block font-sans text-xl antialiased font-semibold leading-snug tracking-normal text-blue-gray-900")
        +"Users list"
    }
}
private fun BODY.insertErrorHandlingScripts() {
    script {
        +"""
            document.body.addEventListener('htmx:responseError', function(evt) {
              alert('An error occurred! HTTP status:' + evt.detail.xhr.status);
            });
            
            document.body.addEventListener('htmx:sendError', function(evt) {
              alert('Server unavailable!');
            });
        """.trimIndent()
    }
}</pre>



<p>As we can see, we can use the <code>classes</code> in Kotlin HTML DSL to prive classes names as a Set of String values:</p>



<p>classes = setOf(&#8220;m-auto max-w-5xl w-full overflow-hidden&#8221;)</p>



<p>In my case, I prefer simply copy-pasting those values instead of separating them with colons.</p>



<h2 class="wp-block-heading" id="h-refactor-form">Refactor Form</h2>



<p>Then, let&#8217;s update the <code>Form.kt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun FlowContent.insertUserForm() {
    div {
        classes = setOf("mx-auto w-full")
        form {
            attributes["hx-post"] = "/users"
            attributes["hx-target"] = "#users-table"
            attributes["hx-swap"] = "beforeend"
            div {
                classes = setOf("-mx-3 flex flex-wrap")
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        htmlFor = "first-name"
                        +"First Name"
                    }
                    input {
                        classes =
                            setOf("w-full rounded-md border border-[#e0e0e0] bg-white py-3 px-6 text-base font-medium text-[#6B7280] outline-none focus:border-[#6A64F1] focus:shadow-md")
                        type = InputType.text
                        name = "first-name"
                        id = "first-name"
                        placeholder = "First Name"
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        htmlFor = "last-name"
                        +"Last Name"
                    }
                    input {
                        classes =
                            setOf("w-full rounded-md border border-[#e0e0e0] bg-white py-3 px-6 text-base font-medium text-[#6B7280] outline-none focus:border-[#6A64F1] focus:shadow-md")
                        type = InputType.text
                        name = "last-name"
                        id = "last-name"
                        placeholder = "Last Name"
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4")
                    label {
                        classes = setOf("mb-3 block text-base font-medium text-[#07074D]")
                        +"Account enabled"
                    }
                    div {
                        classes = setOf("flex items-center space-x-6 pt-3")
                        div {
                            classes = setOf("flex items-center")
                            input {
                                classes = setOf("h-5 w-5")
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-1"
                                value = "true"
                            }
                            label {
                                classes = setOf("pl-3 text-base font-medium text-[#07074D]")
                                htmlFor = "radio-button-1"
                                +"Yes"
                            }
                        }
                        div {
                            classes = setOf("flex items-center")
                            input {
                                classes = setOf("h-5 w-5")
                                type = InputType.radio
                                name = "enabled-radio"
                                id = "radio-button-2"
                                value = "false"
                                checked = true
                            }
                            label {
                                classes = setOf("pl-3 text-base font-medium text-[#07074D]")
                                htmlFor = "radio-button-2"
                                +"No"
                            }
                        }
                    }
                }
                div {
                    classes = setOf("w-full px-3 sm:w-1/4 pt-8")
                    button {
                        classes =
                            setOf("cursor-pointer rounded-md bg-slate-800 py-3 px-8 text-center text-base font-semibold text-white outline-none")
                        +"Add user"
                    }
                }
            }
        }
    }
}</pre>



<p>Similarly, we don&#8217;t change anything in here apart from adding a bunch of classes. A whooooole bunch of classes 🙂 </p>



<h2 class="wp-block-heading" id="h-modify-user-table">Modify User Table</h2>



<p>As the last step, let&#8217; apply changes to our user table: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun FlowContent.insertUserTable(users: List&lt;User>) {
    div {
        classes = setOf("px-0 overflow-scroll")
        table {
            classes = setOf("w-full mt-4 text-left table-auto min-w-max")
            thead {
                tr {
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"User"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"Status"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                        +"Created At"
                    }
                    th {
                        classes = setOf("p-4 border-y border-blue-gray-100 bg-blue-gray-50/50")
                    }
                }
            }
            tbody {
                id = "users-table"
                users.forEach { user ->
                    tr {
                        insertUserRowCells(user)
                    }
                }
            }
        }
    }
}
fun TR.insertUserRowCells(user: User) {
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        div {
            classes = setOf("flex items-center gap-3")
            img {
                classes = setOf("relative inline-block h-9 w-9 !rounded-full object-cover object-center")
                src = "/img/placeholder.png"
            }
            p {
                classes = setOf("block font-sans text-sm antialiased font-normal leading-normal text-blue-gray-900")
                +"${user.firstName} ${user.lastName}"
            }
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        div {
            classes = setOf("w-max")
            div {
                val enabledLabel = if (user.enabled) "Enabled" else "Disabled"
                val labelColor = if (user.enabled) "green" else "red"
                classes =
                    setOf("relative grid items-center px-2 py-1 font-sans text-xs font-bold text-black-900 uppercase rounded-md select-none whitespace-nowrap bg-$labelColor-500/20")
                span { +enabledLabel }
            }
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        p {
            classes = setOf("block font-sans text-sm antialiased font-normal leading-normal text-blue-gray-900")
            +user.createdAt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        }
    }
    td {
        classes = setOf("p-4 border-b border-blue-gray-50")
        button {
            classes =
                setOf("cursor-pointer relative h-10 max-h-[40px] w-10 max-w-[40px] select-none rounded-lg text-center align-middle font-sans text-xs font-medium uppercase text-gray-900 transition-all hover:bg-gray-900/10 active:bg-gray-900/20 disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none")
            attributes["hx-delete"] = "/users/${user.id}"
            attributes["hx-swap"] = "outerHTML"
            attributes["hx-target"] = "closest tr"
            unsafe {
                +"""
                    &lt;span class="absolute transform -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
                        &lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                             stroke-width="2" stroke="currentColor" class="w-6 h-6">
                            &lt;path stroke-linecap="round" stroke-linejoin="round"
                                  d="M6 18L18 6M6 6l12 12"/>
                        &lt;/svg>
                    &lt;/span>
                """.trimIndent()
            }
        }
    }
}</pre>



<p>And here, apart from the CSS classes, we also added the <code>X</code> icon with the <code>unsafe</code> function from Kotlin HTML DSL:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">unsafe {
    +"""
        &lt;span class="absolute transform -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2">
            &lt;svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
                 stroke-width="2" stroke="currentColor" class="w-6 h-6">
                &lt;path stroke-linecap="round" stroke-linejoin="round"
                      d="M6 18L18 6M6 6l12 12"/>
            &lt;/svg>
        &lt;/span>
    """.trimIndent()
}</pre>



<p>And voila! When we run the application now, we should see a pretty decent-looking UI😉</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>That&#8217;s all for this tutorial on how to work with htmx and Kotlin HTML DSL in Ktor.</p>



<p>Again, if you are tired of wasting your time looking for good Ktor resources, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>:</p>



<p>▶️ Over 15 hours of video content divided into over 130 lessons</p>



<p>🛠️ Hands-on approach: together, we implement 4 actual services</p>



<p>✅ top technologies: you will learn not only Ktor, but also how to integrate it with modern stack including JWT, PostgreSQL, MySQL, MongoDB, Redis, and Testcontainers.</p>



<p>Lastly, you can find the source code for this lesson in <a href="https://github.com/codersee-blog/ktor-htmx" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>
<p>The post <a href="https://blog.codersee.com/quick-quide-to-htmx-kotlin/">A Quick Guide to htmx in Kotlin</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/quick-quide-to-htmx-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Auto-reload in Ktor</title>
		<link>https://blog.codersee.com/ktor-auto-reload/</link>
					<comments>https://blog.codersee.com/ktor-auto-reload/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 25 Mar 2025 18:48:04 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=18012802</guid>

					<description><![CDATA[<p>In this, short quide, I will show you how to enable the development mode and auto-reload feature in Ktor application.</p>
<p>The post <a href="https://blog.codersee.com/ktor-auto-reload/">Auto-reload in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Together, we will learn how to enable the Ktor<strong> development mode</strong> in several ways and how <strong>auto-reload</strong> can help with the <strong>Kotlin HTML DSL app</strong> example. </p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/ktor-auto-reload/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F0SHrXcyJhRs%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-quick-ktor-setup">Quick Ktor Setup</h2>



<p>As the first step, let&#8217;s quickly prepare a basic Ktor application responding with HTML.</p>



<p>To do so, let&#8217;s navigate to the <a href="https://start.ktor.io" target="_blank" rel="noreferrer noopener">https://start.ktor.io</a> page and configure the artifact first:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="465" src="http://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-1024x465.png" alt="" class="wp-image-18012803" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-1024x465.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-300x136.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings-768x348.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_artifact_settings.png 1221w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The choice is up to you here. From my end, I will be using the latest Ktor version with the YAML config and without the version catalog. </p>



<p>Then, let&#8217;s add the Kotlin HTML DSL plugin necessary for our simple example:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="446" src="http://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-1024x446.png" alt="" class="wp-image-18012804" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-1024x446.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-300x131.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection-768x335.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/start_ktor_io_new_project_html_dsl_plugin_selection.png 1225w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Lastly, let&#8217;s download the zip package, extract it, and import it to our IDE. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you are working in IntelliJ IDEA, then please make sure that you use JDK 23, too. </p>



<p>You can do that in two places: </p>



<ul class="wp-block-list">
<li>Settings -&gt; Build, Execution, Deployment -&gt; Build Tools -&gt; Gradle. Here, please set the Gradle JVM to 23</li>



<li>Project Structure -&gt; Project Settings -&gt; Project. On this page, please set the SDK to 23 and Language level to &#8220;SDK default&#8221;</li>
</ul>



<p>After all, please sync the gradle project and wait for the process to finish.</p>
</blockquote>



<h2 class="wp-block-heading" id="h-expose-endpoint-returning-html">Expose Endpoint Returning HTML</h2>



<p>With all of that done, let&#8217;s delete the unwanted files that were generated automatically, like <code>Routing.kt</code>, or <code>Templating.kt</code> and expose the endpoint:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.module() {

    routing {
        get("/") {
            call.respondHtml {
                body {
                    + "Hello world!"
                }
            }
        }
    }

}
</pre>



<p>As we can see, whenever we reach the root path (by default, the <code>localhost:8080</code>), we should see the &#8220;Hello world!&#8221; text.</p>



<h2 class="wp-block-heading" id="h-the-problem">The Problem</h2>



<p>Now, let&#8217;s change the text, for example, to contain only a &#8220;Hello!&#8221; message. </p>



<p>What happens if we refresh the page? <strong>Nothing!</strong> The page does not reflect our code change until we restart the server manually! </p>



<p>And, especially when dealing with HTML, it may become a pretty tedious task.</p>



<h2 class="wp-block-heading" id="h-solution-ktor-auto-reload">Solution- Ktor auto-reload</h2>



<p>It won&#8217;t be a big surprise if I say that the <strong>Ktor auto-reload</strong> feature may be a great solution here? 😉 </p>



<p>But what is that?</p>



<p>Well, long story short, it its a feature that we can turn on by <strong>enabling the development mode</strong> in Ktor. And when enabled, it <strong>reloads application classes on code changes</strong>. </p>



<p>To be more specific, according to the documentation, Ktor listens for the changes to the following files:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">/build/classes/kotlin/main/META-INF
/build/classes/kotlin/main/com/your-package-namn
/build/classes/kotlin/main/com
/build/classes/kotlin/main
/build/resources/main</pre>



<p>And as we can see, by the <strong>code changes</strong>, we mean the <strong>generated outputs and artifacts.</strong> But the good news is that with gradle, we can easily automate the generation, too.</p>



<p>Lastly, I just wanted to mention that application logs are also pretty clear about what we need to do in order to enable auto-reload: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">2025-03-25 06:40:49.929 [main] INFO  Application - Autoreload is disabled because the development mode is off.
2025-03-25 06:40:50.072 [main] INFO  Application - Application started in 0.374 seconds.
2025-03-25 06:40:50.363 [DefaultDispatcher-worker-2] INFO  Application - Responding at http://127.0.0.1:8080</pre>



<p></p>



<h2 class="wp-block-heading" id="h-enable-development-mode">Enable development mode</h2>



<p>With all of that said, let&#8217;s get back to the practice part. </p>



<p>As the first step, we must enable the development mode. And we can do that in a few ways, so let me walk you through them.</p>



<h2 class="wp-block-heading" id="h-hardcoded-approach">Hardcoded Approach</h2>



<p>When we navigate to the <code>build.gradle.kts</code> file, we should see the following: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">application {
    mainClass = "io.ktor.server.netty.EngineMain"

    val isDevelopment: Boolean = project.ext.has("development")
    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
}</pre>



<p>So the easy and the dummy approach would be to simply hardcode the value- <code>-Dio.ktor.development=true</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">application {
    mainClass = "io.ktor.server.netty.EngineMain"
    applicationDefaultJvmArgs = listOf("-Dio.ktor.development=true")
}
</pre>



<p>Now, when we run the app with <code>./gradlew run</code>, we will see that the message disappeared, proving the development mode is enabled:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">2025-03-25 07:10:15.883 [main] INFO  Application - Application started in 0.41 seconds.
2025-03-25 07:10:16.142 [main] INFO  Application - Responding at http://127.0.0.1:8080
&lt;==========---> 83% EXECUTING [1m 13s]
</pre>



<p>But first, this approach does not work if you run the server in IntelliJ by using the green icon because, by default, it does not invoke any gradle command.</p>



<p>Secondly, it is hardcoded, meaning we don&#8217;t have too much control over it in various environments. </p>



<h2 class="wp-block-heading" id="h-program-argument">Program Argument</h2>



<p>Another option is to bring back what we already had in the <code>build.gradle.kts</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val isDevelopment: Boolean = project.ext.has("development")
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")</pre>



<p>And from now on, the only thing we need is the <code>-Pdevelopment</code> flag for the <code>gradlew run</code> command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew run -Pdevelopment</pre>



<p>It&#8217;s clearly better and gives us more flexibility. </p>



<h2 class="wp-block-heading" id="h-vm-options">VM options</h2>



<p>Another option that we have on the table is to directly use the VM option we saw above- <code>-Dio.ktor.development=true</code>. </p>



<p>For that purpose, we can even completely get rid of the previous snippet from <code>application</code> in <code>build.gradle.kts</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">application {
    mainClass = "io.ktor.server.netty.EngineMain"
}</pre>



<p>And now, whenever invoking the <code>gradlew run</code> command, we will simply put the whole arg: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew "-Dio.ktor.development=true" run</pre>



<p><strong>Moreover</strong>, when working with IntelliJ, we can edit our configuration to pass the same option: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="693" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-1024x693.png" alt="" class="wp-image-18012814" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-1024x693.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/image-300x203.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/image-768x520.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/image.png 1158w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>So we can see that this option will work with <strong>both gradle, as well as the IntelliJ IDEA runs.</strong> </p>



<h2 class="wp-block-heading" id="h-config-file">Config File</h2>



<p>Lastly, we can also set that in the <code>application.yaml</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ktor:
    development: true
    application:
        modules:
            - com.codersee.ApplicationKt.module
    deployment:
        port: 8080</pre>



<p>It works exactly the same, and with environment variables can give us a lot of flexibility, too. </p>



<h2 class="wp-block-heading" id="h-automate-build">Automate Build</h2>



<p>Alright, so at this point, our app is <strong>up and running</strong> with the <strong>development mode ON.</strong> </p>



<p>However, when we make any code change, <strong>nothing still happens!</strong> </p>



<p>Well, as I mentioned previously, the Ktor auto-reload feature looks for the output files. Meaning we must build the project to see the changes. </p>



<p>And we can do that by either running the <code>./gradlew build</code> or by navigating to the Build tab in IntelliJ: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="634" height="95" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-1.png" alt="" class="wp-image-18012818" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-1.png 634w, https://blog.codersee.com/wp-content/uploads/2025/03/image-1-300x45.png 300w" sizes="auto, (max-width: 634px) 100vw, 634px" /></figure>



<p>And although this works and we can now see changes without the restart, it is still not too convenient. </p>



<p>Well, the good news we can slightly adjust the gradlew build command and <strong>automate that:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">./gradlew -t build -x test</pre>



<p>With the <code>-t</code> flag (or <code>--continuous</code>), we enable the <strong>continuous build mode.</strong> Gradle will watch for changes in the source files, and whenever a relevant change is detected, it will automatically rerun the build task. </p>



<p>Additionally, we exclude the <code>test</code> task using the <code>-x</code> flag to speed up the process a little 🙂 </p>



<p>If we would like to add that as a run configuration to IntelliJ, then it is really easy, too: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="319" src="http://blog.codersee.com/wp-content/uploads/2025/03/image-2-1024x319.png" alt="" class="wp-image-18012819" srcset="https://blog.codersee.com/wp-content/uploads/2025/03/image-2-1024x319.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2-300x93.png 300w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2-768x239.png 768w, https://blog.codersee.com/wp-content/uploads/2025/03/image-2.png 1146w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And basically, that&#8217;s all! At this point, we can add the necessary HTML changes <strong>without restarting the Ktor server.</strong> </p>



<h2 class="wp-block-heading" id="h-narrowing-down-watched-files">Narrowing Down Watched Files</h2>



<p>As I mentioned above, Ktor listens for the file updates in the following directories: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">/build/classes/kotlin/main/META-INF
/build/classes/kotlin/main/com/your-package-namn
/build/classes/kotlin/main/com
/build/classes/kotlin/main
/build/resources/main</pre>



<p>If we would like to narrow down the list of watched directories, then we can do that, for example, in the YAML config file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">ktor:
    deployment:
        watch:
            - resources</pre>



<p>The only thing we must specify is the part of the watched patch that we want to keep. </p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this quick tutorial on how to enable the <strong>auto-reload feature in Ktor.</strong></p>



<p>If you found this content useful, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>&#8211; the most comprehensive Ktor course on the market. </p>



<p>Thank you for being here, and see you around! 🙂 </p>
<p>The post <a href="https://blog.codersee.com/ktor-auto-reload/">Auto-reload in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/ktor-auto-reload/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MongoDB with Kotlin Coroutines in Ktor</title>
		<link>https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/</link>
					<comments>https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Wed, 26 Feb 2025 16:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Coroutines]]></category>
		<category><![CDATA[Mongo]]></category>
		<category><![CDATA[MongoDB]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=17012558</guid>

					<description><![CDATA[<p>In this lesson, we are going to learn how to work with MongoDB and coroutines using the MongoDB Kotlin Driver in Ktor.</p>
<p>The post <a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/">MongoDB with Kotlin Coroutines in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this article, I will show you how to work with MongoDB and Kotlin coroutines in a Ktor application. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Note: this article was created based on one of the services we implement in my <a href="https://codersee.com/courses/ktor-server-pro/">Ktor Server Pro course</a>. If you would like to learn how to expose a fully functional REST API, secure the application, and many many more, then don&#8217;t hesitate and secure your spot today😉</p>
</blockquote>



<p>Before we start, I just wanted to mention that at the moment of writing <strong>KMongo is officially deprecated! </strong>Maybe you saw my previous article, maybe you saw some outdated content in some other places. Either way, we should not use that library anymore in favor of the official driver that comes in two flavors:</p>



<ul class="wp-block-list">
<li><strong>MongoDB Kotlin Driver (for applications using coroutines)</strong></li>



<li>MongoDB Kotlin Sync Driver (for apps that require synchronous processing)</li>
</ul>



<h2 class="wp-block-heading" id="h-video-content">Video Content</h2>



<p>As always, if you prefer a video content, then please check out my latest YouTube video:</p>


<figure class="wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F8NfdOv2R-4U%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /><figcaption></figcaption></figure>


<h2 class="wp-block-heading" id="h-create-amp-verify-mongodb-instance">Create &amp; Verify MongoDB Instance</h2>



<p>If you already have a MongoDB instance installed on your machine, feel free to skip this step.</p>



<p>On the other hand, if that is not the case, you can install it using the <a href="https://www.mongodb.com/docs/manual/installation/">official manual</a>. Or, if you just like me have a Docker environment, you can run the following command:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">docker run --name my_awesome_mongo_name -d -p 27017:27017 mongo:8.0.4</pre>



<p>This way, we run the Mongo docker container and expose it&#8217;s port <code>27017</code> .</p>



<p>And to verify it running, we can run the <code>docker ps</code> :</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CONTAINER ID   IMAGE         COMMAND                  CREATED          STATUS          PORTS                      NAMES
9ecd8986ac34   mongo:8.0.4   "docker-entrypoint.s…"   20 seconds ago   Up 19 seconds   0.0.0.0:27017->27017/tcp   my_awesome_mongo_name</pre>



<p>As we can see, it was created successfully and we should be able to connect to it at <code>localhost:27017</code></p>



<p>We can assert that, as well, for example with a free and official tool- <a href="https://www.mongodb.com/try/download/compass">MongoDB Compass</a>:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="599" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-1024x599.png" alt="Image is a screenshot from MongoDB Compass application and shows how to establish a new connection to mongo instance." class="wp-image-17012563" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-1024x599.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-300x176.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass-768x450.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_verify_connection_compass.png 1411w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And if we are able to connect, it means that we can head to the next step. </p>



<h2 class="wp-block-heading" id="h-create-a-new-ktor-project">Create a New Ktor Project</h2>



<p>Nextly, let&#8217;s navigate to the <a href="https://start.ktor.io/" target="_blank" rel="noreferrer noopener">Ktor Project Generator</a> page to create a fresh project from scratch. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>If you use the IntelliJ Ultimate Edition, then you can generate it in your IDE</p>
</blockquote>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="468" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-1024x468.png" alt="Image is a screenshot from Ktor Generator Page and shows necessary settings for our project." class="wp-image-17012564" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-1024x468.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-300x137.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator-768x351.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_create_project_in_generator.png 1230w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, we will be using <strong>Ktor 3.1.0</strong> with <strong>Netty</strong> and configuration in the <strong>YAML</strong> file.</p>



<p>The <strong>important </strong>thing to mention here is that if we want to work with Kotlin coroutines, <strong>we should not select the below MongoDB plugin:</strong></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="373" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-1024x373.png" alt="Image shows MongoDB plugin for Ktor that we should not use when working with coroutines. " class="wp-image-17012565" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-1024x373.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-300x109.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo-768x279.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo.png 1223w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Because as we can see, this adds the MongoDB Kotlin Sync Driver for synchronous processing:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="828" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-1024x828.png" alt="" class="wp-image-17012566" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-1024x828.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-300x243.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview-768x621.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_coroutines_project_generator_sync_mongo_preview.png 1224w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>So, in our case, to keep our Ktor application as vanilla as possible, we will not add any additional plugins. </p>



<p>With that done, let&#8217;s hit the <em>Download</em> button and let&#8217;s import the project to our IDE.</p>



<h2 class="wp-block-heading" id="h-add-mongodb-async-driver-to-ktor">Add MongoDB Async Driver to Ktor</h2>



<p>Before adding the necessary import, let&#8217;s perform a small cleanup. </p>



<p>Firstly, let&#8217;s remove the <code>Routing.kt</code> file- we don&#8217;t need it for this tutorial.</p>



<p>Then, let&#8217;s navigate to the <code>Application.kt</code> and let&#8217;s get rid of routing config, as well. Eventually, we should have something like that: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group=""> package com.codersee

import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
    io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {}</pre>



<p>With that done, let&#8217;s add the MongoDB Kotlin Driver to work with coroutines. </p>



<p>During the configuration, we decided to use the Gradle version catalog. So now, we must navigate to the <code>libs.versions.toml</code> inside the <code>gradle</code> directory and add the <code>mongodb-version</code> along with <code>mongodb-driver-kotlin</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[versions]
kotlin-version = "2.1.10"
ktor-version = "3.1.0"
logback-version = "1.4.14"
mongodb-version = "5.3.1"

[libraries]
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor-version" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" }
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml", version.ref = "ktor-version" }
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host", version.ref = "ktor-version" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" }
mongodb-driver-kotlin = { module = "org.mongodb:mongodb-driver-kotlin-coroutine", version.ref = "mongodb-version" }

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin-version" }
ktor = { id = "io.ktor.plugin", version.ref = "ktor-version" }</pre>



<p>With that done, let&#8217;s navigate to <code>build.gradle.kts</code> and add the following line: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">implementation(libs.mongodb.driver.kotlin)</pre>



<p>Lastly, let&#8217;s sync the gradle project so that the necessary libraries are fetched. </p>



<h2 class="wp-block-heading" id="h-introduce-model-classes">Introduce Model Classes</h2>



<p>Before we can start working with coroutines, we must prepare classes that will be translated into Mongo documents and vice versa. </p>



<p>To do so, let&#8217;s create the <code>Product.kt</code> class and put the following:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.bson.codecs.pojo.annotations.BsonId
import org.bson.types.ObjectId

data class Product(
    @BsonId
    val id: ObjectId? = null,
    val name: String,
    val description: String,
    val price: Double,
    val category: ProductCategory,
    val tags: List&lt;ProductTag>,
)

enum class ProductCategory {
    VIDEO_GAMES, TOOLS, HOME_AND_KITCHEN, FOOD
}

enum class ProductTag {
    EXCLUSIVE, HANDMADE, ORGANIC, BESTSELLER
}</pre>



<p>As we can see, all fields except the <em>id</em> field are plain Kotlin classes. And later, they will be serialized/deserialized 1:1 when saving and retrieving from the database. </p>



<p>When it comes to the <code>id</code> field, we mark it using the <code>@BsonId</code> annotation. Thanks to that, it will be serialized to the <code>_id</code> BSON field that represents a primary key of our document. Moreover, we assign a default null value to it. This way, the value will be generated automatically. </p>



<h2 class="wp-block-heading" id="h-configure-ktor-connection-to-mongodb">Configure Ktor Connection to MongoDB</h2>



<p>As the next step, let&#8217;s connect our Ktor application to the Mongo instance. </p>



<p>And for that purpose, let&#8217;s navigate to <code>Application.kt</code> and implement the following logic: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.module() {
    val settings = MongoClientSettings.builder()
        .applyConnectionString(
            ConnectionString("mongodb://localhost:27017")
        )
        .build()

    val client = MongoClient.create(settings)
    val database = client.getDatabase("application")
    val productCollection = database.getCollection&lt;Product>("products")
}</pre>



<p>Firstly, we instantiate the <em>MongoClientSettings</em> builder. A builder that allows us to configure the connection string for our database. Additionally, if you are looking for more configuration options, like reads or writes repetition, then you should start in there. </p>



<p>Then, we create a new client and pass our settings to it. If you would like to, then you could use another variant of the <code>create</code> function that takes the connection String as an argument:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">public fun create(connectionString: String): MongoClient</pre>



<p>But, in my opinion, using the builder approach is a better choice in terms of extensibility. </p>



<p>With that done, we get the instance of our database, by passing its name. Lastly, we obtain the MongoCollection that we will be injecting later into the product repository. Again, we pass the name of our collection, too.</p>



<p>To verify, let&#8217;s run our application.</p>



<p>As a result, we should see the following text in the logs indicating that everything is perfectly fine: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">[cluster-ClusterId{value='67bd5f4689251d25d5077fb7', description='null'}-localhost:27017] INFO  org.mongodb.driver.cluster - Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, cryptd=false, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=25, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=26289200, minRoundTripTimeNanos=0}</pre>



<h2 class="wp-block-heading" id="h-mongodb-coroutines-crud-operations">MongoDB Coroutines CRUD Operations</h2>



<p>After we did all of that preparation, we can finally create the <code>ProductRepository</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class ProductRepository(
    private val productCollection: MongoCollection&lt;Product>,
) { }</pre>



<p>And inject the collection in <code>Application.kt</code> : </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val productRepository = ProductRepository(productCollection)</pre>



<p>The constructor injection is a great way to make our code easier to test in the future. </p>



<p>Anyway, coming back to the topic, let&#8217;s learn how we can how we can perform basic CRUD operations with coroutines. </p>



<h3 class="wp-block-heading" id="h-persits-products">Persits Products</h3>



<p>Initially, our <code>products</code> collection is empty, so let&#8217;s add the following code to start populating it: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun save(product: Product): Product? {
    val result = productCollection.insertOne(product)

    return result.insertedId
        ?.let { product.copy(id = it.asObjectId().value) }
}</pre>



<p>First of all, we must make our function <em>suspend</em>. Why? Because the <em>insertOne</em> we use is a suspend function, so we must invoke it either from the coroutine or another suspend function. </p>



<p>When it comes to the persisting- the function that we use returns the <code>InsertOneResult</code> that allows us to either get a boolean informing if the write was acknowledged or the generated identifier of the saved product. And in my opinion, reading that and returning a Product instance with the updated field is a quite nice approach. </p>



<p>After that, let&#8217;s get back to the <code>Application.kt</code> and test this function: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val saved = runBlocking {
    productRepository.save(
        Product(
            name = "Product 1",
            description = "Description 1",
            price = 19.99,
            category = ProductCategory.TOOLS,
            tags = listOf(ProductTag.EXCLUSIVE, ProductTag.BESTSELLER)
        )
    )
}

println(saved)</pre>



<p>I know, the good, old println 🤠 </p>



<p>Anyway, if we run our application, we should see the following in the logs: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])</pre>



<p>Additionally, when we hit the <em>refresh</em> button in MongoDB Compass, we should see the following: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="358" src="http://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-1024x358.png" alt="Image shows a screenshot from MongoDB Compass and persisted product with coroutines." class="wp-image-17012580" srcset="https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-1024x358.png 1024w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-300x105.png 300w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view-768x269.png 768w, https://blog.codersee.com/wp-content/uploads/2025/02/ktor_mongodb_created_product_atlas_view.png 1410w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>And this proves that not only the Product was saved. But also, the <code>application</code> database and <code>products</code> collection was created by our client automatically. </p>



<h2 class="wp-block-heading" id="h-find-by-id">Find By ID</h2>



<p> Nextly, let&#8217;s implement the function to fetch product by identifier: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun findById(id: String): Product? {
    val objectId = ObjectId(id)

    return productCollection.find(
        eq("_id", objectId)
    ).firstOrNull()
}</pre>



<p>This time, we use the <code>find</code> method. And this function returns the <em>FindFlow</em> which is the <em>Flow </em>implementation for find operations. </p>



<p>This function allows us to pass filters as arguments to it. And to get the particular product, we use the <strong>eq </strong>filter. One of the many filters that we can find in <code>com.mongodb.client.model.Filters</code>. We must remember that our identifier is of the <em>ObjectId</em> type, so we create a new instance from our String value. </p>



<p>Lastly, we invoke the <code>firstOrNull</code>&#8211; the terminal operator that returns the first element emitted by the flow and then cancels flow&#8217;s. We leverage the fact that only one element with such an identifier can be found in our database.</p>



<p>So with all of that done, let&#8217;s get back to the <code>Application.kt</code> and test this function: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val found = runBlocking {
    productRepository.findById("67bd62f974edd96bbd59874a")
}

val notFound = runBlocking {
    productRepository.findById("67bd62f974edd96bbd59874b")
}

// Logs: 

// Product(id=67bd62f974edd96bbd59874a, name=Product 1, description=Description 1, price=19.99, category=TOOLS, tags=[EXCLUSIVE, BESTSELLER])

// null</pre>



<p>As we can see, our test proves that <em>findById</em> not only works but also it simply returns null when nothing was found. No unexpected exceptions, etc. </p>



<h3 class="wp-block-heading" id="h-updating-products">Updating Products</h3>



<p>As the next step, let&#8217;s add the function responsible for updating products: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun update(id: String, product: Product): Product? {
    val objectId = ObjectId(id)

    return productCollection.findOneAndReplace(
        filter = eq("_id", objectId),
        replacement = product,
        options = FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)
    )
}</pre>



<p>Again, this function is a suspended function, and again we use the same combination of the <em>eq</em> filter and <em>ObjectId</em> to find the item we are interested in. </p>



<p>The function that we use- <code>findOneAndReplace</code>&#8211; allows us to simply replace the existing document by passing a new version. Nevertheless, by default, this function returns the <strong>object before the update!</strong> And in my opinion, it makes more sense to return the updated versions in this case. And that&#8217;s why we specify the additional option. </p>



<p>As a note: if you would like to update just some fields, then the <code>findOneAndUpdate</code> may be a better choice. </p>



<p>With all of that done, let&#8217;s test our functionality: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val updated = runBlocking {
    productRepository.update(
        id = "67bd62f974edd96bbd59874a",
        product = Product(
            name = "Updated Product 1",
            description = "Updated Description 1",
            price = 20.11,
            category = ProductCategory.FOOD,
            tags = listOf(ProductTag.BESTSELLER)
        )
    )
}

val notUpdated = runBlocking {
    productRepository.update(
        id = "67bd62f974edd96bbd59874b",
        product = Product(
            name = "Updated Product 2",
            description = "Updated Description 3",
            price = 20.11,
            category = ProductCategory.FOOD,
            tags = listOf(ProductTag.BESTSELLER)
        )
    )
}

// Logs: 

// Product(id=67bd62f974edd96bbd59874a, name=Updated Product 1, description=Updated Description 1, price=20.11, category=FOOD, tags=[BESTSELLER])

// null</pre>



<p>As we can see, everything works as expected. Our function returns the <strong>updated</strong> product. And moreover, it does not throw any exceptions when a product is not found! </p>



<h3 class="wp-block-heading" id="h-delete-products">Delete Products</h3>



<p>Nextly, let&#8217;s take a look at how we can remove products from our database: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun deleteById(id: String): Boolean {
    val objectId = ObjectId(id)

    val deleteResult = productCollection.deleteOne(
        eq("_id", objectId)
    )

    return deleteResult.deletedCount == 1L
}</pre>



<p>At this point of our MongoDB coroutines tutorial, I think the code is quite descriptive. We use the same pattern we did previously to find by ID and we utilize the function from <code>DeleteResult</code> to get the count of deleted items. </p>



<p>Of course, given we have only one item with a particular <code>_id</code>, the function returns <em>true</em> only if the count is equal to one. </p>



<p>And again, a small note from my end if you would like to return the deleted product instead, then you can use the <code>findOneAndDelete</code> instead.</p>



<p>I will skip the testing part here, you must trust me 😀</p>



<h3 class="wp-block-heading" id="h-case-insensitive-search-in-mongodb">Case-insensitive Search In MongoDB</h3>



<p>As the last step, I will show you how to utilize the function that we already know (<code>find</code>) to perform a case-insensitive search in MongoDB: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun find(
    title: String,
): Flow&lt;Product> {
    return productCollection.find(
        regex(Product::name.name, title, "i")
    )
}</pre>



<p>As we can see, this time we make use of the <code>regex</code> function, that will add the&#8230; regex filter 😀 And thanks to the <strong>&#8220;i&#8221;</strong> option, the whole search will be case-insensitive.</p>



<p>Additionally, we make use of the <em>name</em> field reference. We could use a simple String value- &#8220;name&#8221;- but thanks to our approach we won&#8217;t need to remember to manually update the name in case of the update. </p>



<p>Lastly, I just wanted to mention here that this is one of the approaches to tackle this issue. According to the Mongo docs, we have also two more options: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>1. Create a case-insensitive index with a collation strength of 1 or 2, and specify that your query uses the same collation.<br>2. Set the default collation strength of your collection to 1 or 2 when you create it, and do not specify a different collation in your queries and indexes.</p>
</blockquote>



<h3 class="wp-block-heading" id="h-adding-sorting-and-pagination-to-search-results">Adding Sorting and Pagination to Search Results</h3>



<p>Lastly, let&#8217;s make a small adjustment to add pagination and sorting to our logic.</p>



<p>To do so, let&#8217;s implement the <code>Order</code> Enum first:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">enum class Order {
    ASC, DESC
}</pre>



<p>And with that done, let&#8217;s get back to our repository: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun find(
    title: String,
    sortBy: String,
    order: Order,
    limit: Int,
    skip: Int,
): Flow&lt;Product> {
    val sort = when (order) {
        Order.ASC -> ascending(sortBy)
        Order.DESC -> descending(sortBy)
    }

    return productCollection.find(
        regex(Product::name.name, title, "i")
    )
        .sort(sort)
        .limit(limit)
        .skip(skip)
}</pre>



<p>As we can see, our function allows us to pass 4 more arguments during the invocation: <em>sortBy</em>, <em>order</em>, <em>limit</em>, and <em>skip</em>.</p>



<p>So from now on, we can not only perform the search but also set the limit of results, ordering, as well as the order of items.</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on how to work with MongoDB and Kotlin coroutines in Ktor. </p>



<p>I hope you enjoyed it, and if you would like to learn more Ktor concepts in a fully hands-on manner, then check out my <a href="https://codersee.com/courses/ktor-server-pro/">course</a>. </p>



<p>Lastly, if you would like to get the whole codebase, then you can find it in <a href="https://github.com/codersee-blog/ktor-mongodb-coroutines" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p></p>
<p>The post <a href="https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/">MongoDB with Kotlin Coroutines in Ktor</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/mongodb-kotlin-coroutines-ktor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Create a Ktor Client To Connect To OpenWeatherMap API</title>
		<link>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/</link>
					<comments>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/#respond</comments>
		
		<dc:creator><![CDATA[Peter Lantukh]]></dc:creator>
		<pubDate>Wed, 17 Jan 2024 05:15:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Ktor Client]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9508613</guid>

					<description><![CDATA[<p>This article provides all the necessary information you need to know about setting up a Ktor client with OpenWeatherMap API.</p>
<p>The post <a href="https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/">How To Create a Ktor Client To Connect To OpenWeatherMap API</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This comprehensive guide is about setting up a Ktor client that effortlessly retrieves data from OpenWeatherMap API. I&#8217;ll tackle client configuration, API calls, models configuration, and <strong>error handling</strong>, leaving you with a weather-fetching client ready to be used on any platform.</p>



<p><strong>Ktor</strong> is a flexible library that provides the ability to create a non-blocking <strong>HTTP client</strong>, which enables you to perform requests and handle responses. Its functionality could be enhanced with plugins, such as logging, serialization, authentication, and so on. Its core benefit is that it&#8217;s a lightweight framework that can be used on different platforms such as <strong>Android, JVM, or Native</strong>!</p>



<h2 class="wp-block-heading" id="h-general-overview">General Overview</h2>



<p>Let&#8217;s discuss some basic information for everyone to be on the same page.  </p>



<p>Today, we are going to write HTTP requests and receive responses. There are different types of requests but OpenWeatherMap for the most part utilizes <strong>GET requests</strong> with various parameters.</p>



<p>OpenWeatherMap API, as you can tell by its name, is an API for retrieving weather-related data. It provides various types of data but we&#8217;re going to use only 3 of them:</p>



<ul class="wp-block-list">
<li>Current Weather, </li>



<li>3-hours Forecast, </li>



<li>and Air Pollution API.</li>
</ul>



<p>To start using OpenWeatherMap <a href="https://home.openweathermap.org/subscriptions/unauth_subscribe/onecall_30/base" target="_blank" rel="noreferrer noopener nofollow">you need to register</a> and get your special API key. It has lots of free options and for our sake, we don&#8217;t need to pay at all. </p>



<p>But be aware of the limitations of the call: <strong>1,000 API calls per day for free</strong>!</p>



<h2 class="wp-block-heading" id="h-gradle-dependencies">Gradle Dependencies</h2>



<p>For this project, we&#8217;re going to use basic <strong>Ktor&#8217;s client, serialization, engines, and logging</strong>. </p>



<p>Your <strong>build.gradle.kts</strong> file would look like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val ktor_version: String by project
val kotlin_version: String by project
val logback_version: String by project

plugins {
    kotlin("jvm") version "1.9.21"
    id("io.ktor.plugin") version "2.3.6"
    id("org.jetbrains.kotlin.plugin.serialization") version "1.9.21"
    application
}

repositories {
    mavenCentral()
}

dependencies {
    implementation("io.ktor:ktor-client-core-jvm")
    implementation("io.ktor:ktor-client-cio-jvm")

    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
    implementation("io.ktor:ktor-client-content-negotiation-jvm")

    implementation("ch.qos.logback:logback-classic:$logback_version")
    implementation("io.ktor:ktor-client-logging:$ktor_version")
    implementation("io.ktor:ktor-client-apache5:$ktor_version")

    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}</pre>



<h2 class="wp-block-heading" id="h-creating-models">Creating Models</h2>



<p>The first step of creating a Ktor client is actually revealing what we&#8217;re gonna handle. Basically, we need <strong>2</strong> types of models: <strong>request and response</strong>. All necessary information is located on the corresponding page of the documentation. The documentation is pretty neat and, generally, it&#8217;s a good practice to check it out during the coding!</p>



<p>To optimize the process we could spot similar parameters appearing in the Current Weather and Weather Forecast. Such as Mode and Units. </p>



<p>So we can create separate classes and use the latter like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">enum class ResponseUnits {
    STANDARD,
    METRIC,
    IMPERIAL
}

enum class ResponseMode {
    XML
}
</pre>



<p>Now let&#8217;s create the request themselves. </p>



<p>As there are some optional parameters present we&#8217;re going to make them nullable with default value null so that we could use them or not with ease. </p>



<p>Here are the models for requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class CurrentWeatherRequest(
    val latitude: Double,
    val longitude: Double,
    val mode: ResponseMode? = null,
    val units: ResponseUnits? = null,
    val language: String? = null
)

data class WeatherForecastRequest(
    val latitude: Double,
    val longitude: Double,
    val mode: ResponseMode? = null,
    val units: ResponseUnits? = null,
    val language: String? = null,
    val count: Int? = null
)

data class AirPollutionRequest(
    val latitude: Double,
    val longitude: Double
)</pre>



<p>Consequently, we desperately need models to put our data to: <strong>the response models</strong>. </p>



<p>It&#8217;s the most routine process because there are some huge responses and by &#8216;huge&#8217; I mean lots of parameters. Let&#8217;s dive deep into one model and the rest would be easy to create. </p>



<p>Here&#8217;s what our <strong>JSON</strong> of current weather data looks like (there is also a text representation of this JSON with more information about parameters):</p>



<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">{
  "coord": {
    "lon": 10.99,
    "lat": 44.34
  },
  "weather": [
    {
      "id": 501,
      "main": "Rain",
      "description": "moderate rain",
      "icon": "10d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 298.48,
    "feels_like": 298.74,
    "temp_min": 297.56,
    "temp_max": 300.05,
    "pressure": 1015,
    "humidity": 64,
    "sea_level": 1015,
    "grnd_level": 933
  },
  "visibility": 10000,
  "wind": {
    "speed": 0.62,
    "deg": 349,
    "gust": 1.18
  },
  "rain": {
    "1h": 3.16
  },
  "clouds": {
    "all": 100
  },
  "dt": 1661870592,
  "sys": {
    "type": 2,
    "id": 2075663,
    "country": "IT",
    "sunrise": 1661834187,
    "sunset": 1661882248
  },
  "timezone": 7200,
  "id": 3163858,
  "name": "Zocca",
  "cod": 200
}</pre>



<p>Now we have to face some difficulties. Not only do we have to parse it but also there are some <strong>optional parameters</strong>. </p>



<p>To solve the first issue we mark our classes with <strong>@Serializable</strong> and <strong>@SerialName</strong> annotations. I highly recommend using @SerialName because it provides <strong>enhanced readability</strong> and usability especially when handling such long classes. </p>



<p>To solve the second problem I recommend you to read my <a href="https://blog.codersee.com/kotlinx-serialization-in-kotlin-all-you-need-to-know/" target="_blank" rel="noreferrer noopener">detailed guide</a> that covers all essential information about Serialization. Basically, if we want to parse all of the data, we make <strong>optional fields</strong> with <strong>@EncodeDefault</strong> and make it <strong>nullable with the default value as null</strong>. This makes sure that if there was no such a field we would get a null, without any exceptions. </p>



<p>Now let&#8217;s look at classes for our Ktor client:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class CurrentWeather(
    @SerialName("coord") val coordinates: Coordinates,
    @SerialName("weather") val weather: List&lt;Weather&gt;,
    @SerialName("base") val base: String,
    @SerialName("main") val main: Main,
    @SerialName("visibility") val visibility: Int,
    @SerialName("wind") val wind: Wind,
    @SerialName("rain")
    @EncodeDefault
    val rain: Rain? = null,
    @SerialName("snow")
    @EncodeDefault
    val snow: Snow? = null,
    @SerialName("clouds") val clouds: Clouds,
    @SerialName("dt") val dateTime: Long,
    @SerialName("sys") val systemData: SystemData,
    @SerialName("timezone") val timezone: Int,
    @SerialName("id") val cityId: Int,
    @SerialName("name") val cityName: String,
    @SerialName("cod") val code: Int
) {

    //other classes here

    @Serializable
    data class Wind(
        @SerialName("speed") val speed: Double,
        @SerialName("deg") val direction: Int,
        @SerialName("gust") val gust: Double
    )

    @Serializable
    data class Rain(
        @SerialName("1h")
        @EncodeDefault
        val oneHour: Double? = null,
        @SerialName("3h")
        @EncodeDefault
        val threeHours: Double? = null
    )

	//other classes here
}</pre>



<p>The rest of the responses are quite similar so you could check them out in the repository that comes with this article. </p>



<p>Quick remark, I&#8217;ve made classes such as Coordinates, Weather, etc. as inner classes since there are the same classes for other responses but with different structures. </p>



<p>This approach helps not to mix them up.</p>



<h2 class="wp-block-heading" id="h-creating-the-ktor-client-itself">Creating The Ktor Client Itself</h2>



<p>We are gonna be using <strong>Ktor&#8217;s HttpClient</strong> with some adjustments and extended configuration. </p>



<p>Let&#8217;s check it out:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(Apache5) {
        install(ContentNegotiation) {
            json(Json {
                isLenient = false
            })
        }
        install(Logging)
        defaultRequest {
            url {
                protocol = URLProtocol.HTTPS
                host = "api.openweathermap.org/data/2.5"
            }
        }
    }</pre>



<p>First of all, we have the <strong>Apache5</strong> engine rather than <strong>CIO</strong>, for example, because it&#8217;s suitable for <strong>HTTP/2</strong> while CIO is not. </p>



<p>Next, we have 2 plugins: the <strong>ContentNegotiation</strong> for the <strong>JSON</strong> parsing and the <strong>Logging</strong> for detailed logs. </p>



<p>Also, as all of our requests to OpenWeatherMap have similar endpoints, we can use <strong>defaultRequest</strong> to shorten our request&#8217;s links. The whole defaultRequest translates to https://api.openweathermap.org/data/2.5/. We&#8217;ll combine this with the request&#8217;s specific <strong>parameters</strong>. </p>



<p>Now let&#8217;s talk about them.</p>



<h2 class="wp-block-heading" id="h-creating-routes">Creating Routes</h2>



<p>As all of our routes would share similar logic let&#8217;s look closely at only one of them for the Current Weather:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun getCurrentWeather(currentWeatherRequest: CurrentWeatherRequest): CurrentWeather? {
        return try {
            val response: HttpResponse = client
                .get("/weather") {
                    url {
                        parameters.append("lat", currentWeatherRequest.latitude.toString())
                        parameters.append("lon", currentWeatherRequest.longitude.toString())
                        parameters.append("appid", APP_ID)
                        if (currentWeatherRequest.mode != null) parameters.append(
                            "mode",
                            currentWeatherRequest.mode.name()
                        )
                        if (currentWeatherRequest.units != null) parameters.append(
                            "units",
                            currentWeatherRequest.units.name()
                        )
                        if (currentWeatherRequest.language != null) parameters.append(
                            "lang",
                            currentWeatherRequest.language
                        )
                    }
                }

            if (response.status == HttpStatusCode.OK) {
                response.body&lt;CurrentWeather&gt;()
            } else {
                println("Failed to retrieve current weather. Status: ${response.status}")
                null
            }
        } catch (e: Exception) {
            println("Error retrieving current weather: ${e.message}")
            null
        }
    }</pre>



<p>Here we create <strong>GET HttpResponse</strong> with the path &#8220;/weather&#8221; and then we specify <strong>additional parameters</strong>, note that it&#8217;ll automatically use the correct notation(? and other). </p>



<p>In the URL block, we list all of our parameters using <strong>parameters.append()</strong> function that takes the name and the value. There is no need to include <strong>optional parameters</strong> that are not specified, so we simply check whether they&#8217;re null or not.</p>



<p>Our Ktor client also implements <strong>error handling</strong>. </p>



<p>For the errors with the request itself, such as connection problems, any issues from the server, etc. we&#8217;ll get the &#8220;Error retrieving current weather:&#8221; message and null result. </p>



<p>And if your request is successful but has some undesired response, in our case everything except 200 OK, we&#8217;ll get &#8220;Failed to retrieve current weather. Status:&#8221; and null result as well.</p>



<h2 class="wp-block-heading" id="h-ktor-client-in-action">Ktor Client In Action</h2>



<p>To use the client you could simply call our object with the corresponding method like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">WeatherClient.getCurrentWeather(
        CurrentWeatherRequest(
            latitude = 44.34,
            longitude = 10.99,
            units = ResponseUnits.IMPERIAL,
            language = "pl"
        )
    )
	
	WeatherClient.getWeatherForecast(
        WeatherForecastRequest(
            latitude = 44.34,
            longitude = 10.99,
            units = ResponseUnits.IMPERIAL,
            language = "pl"
        )
    )
	
	WeatherClient.getAirPollution(
        AirPollutionRequest(
            latitude = 50.0,
            longitude = 50.0
        )
    )</pre>



<p>This could be used on every platform but as I&#8217;ve mentioned before make sure you&#8217;re using the correct engine!</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that’s all for this article about the <strong>OpenWeatherMap API Ktor client</strong>.</p>



<p>If you would like to download the full source code, then you can find it in this <a href="https://github.com/codersee-blog/kotlin-ktor-client-weather">GitHub repository</a>.</p>



<p>Lastly, thank you for being here, and happy to hear your feedback in the comments section below!</p>
<p>The post <a href="https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/">How To Create a Ktor Client To Connect To OpenWeatherMap API</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-create-a-ktor-client-to-connect-to-openweathermap-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How To Use kotlinx.serialization with Ktor and Kotlin?</title>
		<link>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/</link>
					<comments>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/#respond</comments>
		
		<dc:creator><![CDATA[Peter Lantukh]]></dc:creator>
		<pubDate>Tue, 02 Jan 2024 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[kotlinx.serialization]]></category>
		<category><![CDATA[Ktor Client]]></category>
		<category><![CDATA[Ktor Server]]></category>
		<category><![CDATA[Serialization]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008447</guid>

					<description><![CDATA[<p>This article is all you need to know about how to set up kotlinx.serialization library in Ktor Server, Client, and WebSockets. </p>
<p>The post <a href="https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/">How To Use kotlinx.serialization with Ktor and Kotlin?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you would like to learn how to use the <strong>kotlinx.serialization library with Ktor</strong> and Kotlin, then you just came to the right place! In this article, I will show you how to configure it to work with: </p>



<ul class="wp-block-list">
<li>Ktor Server</li>



<li>Ktor Client </li>



<li>WebSockets in Ktor</li>
</ul>



<p>Before we start, I just wanted to note that we are going to use a modified project from the lesson about <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/" target="_blank" rel="noreferrer noopener">Ktor with Ktorm and PostgreSQL</a>. So if you don&#8217;t know how to set up and use a Ktor server with PostgreSQL you should definitely check it up, because I will skip the basics. Additionally, if you&#8217;d like to explore kotlinx.serialization in details, then check out my <a href="https://blog.codersee.com/kotlinx-serialization-in-kotlin-all-you-need-to-know/" target="_blank" rel="noreferrer noopener">detailed guide</a>.</p>



<p>Lastly, please note that there is a <strong>GitHub repository</strong> for all of this in the end of this lesson!</p>



<h2 class="wp-block-heading" id="h-setup">Setup</h2>



<p>A few quick words about setting up. Note that for the main thing we are going to use, such as <strong>ContentNegotiation</strong>, Ktor has <strong>different implementations for Client and Server</strong>. </p>



<p>Do not mess them up, it causes a lot of trouble and a lot of Stack Overflow questions 🙂</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">//server
implementation("io.ktor:ktor-server-content-negotiation-jvm")
//client
implementation("io.ktor:ktor-client-content-negotiation-jvm")</pre>



<h2 class="wp-block-heading" id="h-kotlinx-serialization-with-ktor-server">kotlinx.serialization With Ktor Server</h2>



<p>Now, let&#8217;s start with the kotlinx.serialization for the Ktor server part. </p>



<p>I am going to use <strong>Postman</strong> for making HTTP<strong> requests</strong> and testing the server. You can use any tool for this purpose even the client we will write about in the next part 🙂</p>



<p>First of all, let&#8217;s check our classes for the User from previous parts:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">interface User : Entity&lt;User> {  
  companion object : Entity.Factory&lt;User>()  
  
  val userId: Long?  
  var userName: String  
}</pre>



<p>&nbsp;Now we are going to need classes for our responses:&nbsp;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Serializable  
data class UserResponse(  
  val userId: Long,  
  val userName: String  
)  
  
@Serializable  
data class UserRequest(  
  val userName: String  
)

@Serializable  
data class UserErrorResponse(val message: String)</pre>



<p>Basically, the central part of all serialization here is <strong>ContentNegotiation</strong> which could be installed for our server(not the client). </p>



<p>As we are using it for Json serialization, we should specify it as <strong>json()</strong> like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureSerialization() {  
  install(ContentNegotiation) {  
    json(Json {  
      prettyPrint = true  
      isLenient = true  
    })  
  }  
}</pre>



<p>And here&#8217;s our loved <strong>Json</strong> object! Now we can do all kinds of trickery that we know and learned before.</p>



<p>For the code above, I&#8217;ve used a simple style specification, but you can use it as you like. As an example, we can add a custom or polymorphic serializer:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val contextualSerializerModule = SerializersModule {
  contextual&lt;Date>(DateAsStringSerializer)
}

private val polymorphicSerializationModule = SerializersModule {
  polymorphic(User::class) {
    subclass(Admin::class, Admin.serializer())
    subclass(Guest::class, Guest.serializer())
  }
}

object DateAsStringSerializer : KSerializer&lt;Date> {
  private val dateFormat = SimpleDateFormat("yyyy-MM-dd 'T' HH:mm:ss.SSSZ")

  override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(
    "Date", 
    PrimitiveKind.STRING,
  )

  override fun serialize(encoder: Encoder, value: Date) {
    encoder.encodeString(dateFormat.format(value))
  }

  override fun deserialize(decoder: Decoder): Date {
    return dateFormat.parse(decoder.decodeString())
  }
}
</pre>



<p>And our server is going to look like this:&nbsp;</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json(Json {
      prettyPrint = true
      isLenient = true
      serializersModule = contextualSerializerModule
      serializersModule = polymorphicSerializationModule
    })
  }
}</pre>



<p>We can remove this for now, we&#8217;ll use actual polymorphic serialization in the Web Sockets section.</p>



<p>Now, what about routes? Let&#8217;s check out a simple <strong>POST</strong> request:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.createUser(userService: UserService) {
  post {

    val request = call.receive&lt;UserRequest>()

    val success = userService.createUser(userRequest = request)

    if (success)
      call.respond(HttpStatusCode.Created)
    else
      call.respond(HttpStatusCode.BadRequest, UserErrorResponse("Cannot create user"))
  }
}</pre>



<p>As we can see, we don&#8217;t serialize/deserialize anything manually. </p>



<p>The whole process happens due to this line:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">val request = call.receive&lt;UserRequest>()</pre>



<p>We can witness that our request deserializes into the UserRequest as such is marked with <strong>@Serializable</strong> annotation. </p>



<p>The same happens here:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.updateUserByIdRoute(userService: UserService) {
  patch("/{userId}") {
    val userId: Long = call.parameters["userId"]?.toLongOrNull()
      ?: return@patch call.respond(
          HttpStatusCode.BadRequest, 
          UserErrorResponse("Invalid id")
        )

    val request = call.receive&lt;UserRequest>()
    val success = userService.updateUserById(userId, request)

    if (success)
      call.respond(HttpStatusCode.NoContent)
    else
      call.respond(
        HttpStatusCode.BadRequest,
        UserErrorResponse("Cannot update user with id [$userId]"),
      )
  }
}</pre>



<p>Our request is deserialized and we can update a user. </p>



<p>Now let&#8217;s test it and get our users:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private fun User?.toUserResponse(): UserResponse? =  
    this?.let { UserResponse(it.userId!!, it.userName) }

fun Route.getAllUsersRoute(userService: UserService) {  
    get {  
        val users = userService.findAllUsers()  
            .map(User::toUserResponse)  
  
        call.respond(message = users)  
    }  
}</pre>



<p>As I said before, I’m going to use Postman. </p>



<p>So how about checking our server:</p>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="432" height="108" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4.png" alt="Image shows a GET request URL in Postman. " class="wp-image-9008448" style="aspect-ratio:4;width:432px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4.png 432w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_4-300x75.png 300w" sizes="auto, (max-width: 432px) 100vw, 432px" /></figure>



<figure class="wp-block-image aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="474" height="280" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6.png" alt="Image shows a response from the GET request serialized with kotlinx serialization in our Ktor server" class="wp-image-9008449" style="aspect-ratio:1.6928571428571428;width:474px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6.png 474w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_6-300x177.png 300w" sizes="auto, (max-width: 474px) 100vw, 474px" /></figure>



<p>Post:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="391" height="112" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1.png" alt="Image shows the URL for POST request in Postman" class="wp-image-9008451" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1.png 391w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_5-1-300x86.png 300w" sizes="auto, (max-width: 391px) 100vw, 391px" /></figure>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="990" height="150" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2.png" alt="Screenshot presents the content type header set to application/json" class="wp-image-9008452" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2.png 990w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2-300x45.png 300w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_2-768x116.png 768w" sizes="auto, (max-width: 990px) 100vw, 990px" /></figure>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="388" height="124" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3.png" alt="Screenshot shows the response we get from the POST endpoint in our Ktor server serialized using the kotlinx serialization library" class="wp-image-9008453" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3.png 388w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_3-300x96.png 300w" sizes="auto, (max-width: 388px) 100vw, 388px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="181" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-1024x181.png" alt="Image presents 201 Created response from the POST endpoint" class="wp-image-9008454" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-1024x181.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-300x53.png 300w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1-768x136.png 768w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_1.png 1501w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>Delete:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="349" height="102" src="http://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7.png" alt="Image shows the url path for the DELETE endpoint." class="wp-image-9008455" srcset="https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7.png 349w, https://blog.codersee.com/wp-content/uploads/2023/12/ktor_serialization_7-300x88.png 300w" sizes="auto, (max-width: 349px) 100vw, 349px" /></figure>



<h2 class="wp-block-heading" id="h-kotlinx-serialization-with-ktor-client">kotlinx.serialization With Ktor Client</h2>



<p>Now the kotlinx.serialization for the Ktor client. Here&#8217;s the same thing, but make sure you don&#8217;t miss anything and use <strong>ContentNegotiation FOR CLIENT</strong>. </p>



<p>Check the import:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.client.plugins.contentnegotiation.*</pre>



<p>We can install it just like before:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(CIO) {
  install(ContentNegotiation) {
    json(Json {
      prettyPrint = true
      isLenient = true
    })
  }
  defaultRequest {
    url {
      host = "0.0.0.0"
      path("/")
      port = 8080
    }
  }}
</pre>



<p>All things apply here as well, so let&#8217;s check how to use it for our requests. There are lots of possible situations, we are going to cover basic data retrieving and how to place something inside a <strong>body</strong> of a request.</p>



<p>We want to get all of the users, what should we do? We can use something like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun getAllUsers(): List&lt;UserResponse> {
  return try {
    val response: HttpResponse = client.get("/users")

    if (response.status == HttpStatusCode.OK) {
      response.body&lt;List&lt;UserResponse>>()
    } else {
      println("Failed to retrieve users. Status: ${response.status}")
      emptyList()
    }
  } catch (e: Exception) {
    println("Error retrieving users: ${e.message}")
    emptyList()
  }
}</pre>



<p>The main part here as well is this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">response.body&lt;List&lt;UserResponse>>()</pre>



<p>The UserResponse class can be serialized, and we don&#8217;t need to do anything at all. All hail the <strong>ContentNegotiation</strong> 🙂</p>



<p>How about we create some user? We can do it as well:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun createUser(user: UserRequest) {
  try {
    val response: HttpResponse = client.post("/users") {
      contentType(ContentType.Application.Json)
      setBody(user)
    }

    if (response.status == HttpStatusCode.Created) {
      println("User created successfully")
    } else {
      println("Failed to create user. Status: ${response.status}")
    }
  } catch (e: Exception) {
    println("Error creating user: ${e.message}")
  }
}</pre>



<p>This time, we specify the content type for our body, such as <strong>ContentType.Application.Json</strong> and just set our user in here. All happens as it is a miracle.</p>



<p>Now let’s test it. I’m going to use this simply to check each method:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">runBlocking {

  val allUsersOld = UserRequests.getAllUsers()
  println("All Users: $allUsersOld")

  val newUser = UserRequest(
    userName = "Mark"
  )

  UserRequests.createUser(newUser)

  val allUsersNew = UserRequests.getAllUsers()
  println("All Users: $allUsersNew")

  val userIdToRetrieve = 2L
  val retrievedUser = UserRequests.getUserById(userIdToRetrieve)
  println("User with ID $userIdToRetrieve: $retrievedUser")

  val userIdToUpdate = 2L
  val updatedUser = UserRequest(
    userName = "Bob"
  )
  UserRequests.updateUserById(userIdToUpdate, updatedUser)

  val userIdToDelete = 2L
  UserRequests.deleteUserById(userIdToDelete)
}</pre>



<p>And the corresponding result would be something like this:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">...
All Users: [UserResponse(userId=2, userName=User #2)]
...
User created successfully
...
All Users: [UserResponse(userId=2, userName=User #2), UserResponse(userId=7, userName=Mark)]
...
User with ID 2: UserResponse(userId=2, userName=User #2)
...
User updated successfully
...
User deleted successfully</pre>



<p>I’ve skipped the logs because they are not important for this.&nbsp;</p>



<h2 class="wp-block-heading" id="h-web-sockets">Web Sockets</h2>



<p>So, here&#8217;s the fun part. <strong>There is no Content Negotiation for the Web Sockets</strong> 🙁 </p>



<p>Nonetheless, there is such thing as <strong>contentConverter</strong> for WebSockets:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val client = HttpClient(CIO).config {
  install(WebSockets) {
    contentConverter = KotlinxWebsocketSerializationConverter(
      Json
    )
  }
}</pre>



<p>Basically, that&#8217;s it, now we can use <strong>sendSerialized()</strong> and <strong>receiveDeserialized()</strong> to send and receive data.</p>



<p>However, let&#8217;s focus more on manual implementation for the sake of understanding. Here we have classes to represent our messages:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Serializable
abstract class Message {
  abstract val content: String
}

@Serializable
@SerialName("text")
class TextMessage(override val content: String) : Message()

@Serializable
@SerialName("system")
class SystemMessage(override val content: String, val systemInfo: String) : Message()

private val module = SerializersModule {
  polymorphic(Message::class) {
    subclass(TextMessage::class, TextMessage.serializer())
    subclass(SystemMessage::class, SystemMessage.serializer())
  }
}

val messagesFormat = Json {
  serializersModule = module
}</pre>



<p>We are going to use TextMessage to send to the server and SystemMessage to send back from the server. </p>



<p>To send a message we must serialize it to string. And to receive vice versa. Nothing difficult: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@OptIn(ExperimentalSerializationApi::class)
private suspend fun DefaultClientWebSocketSession.receiveMessage() {
  try {
    for (message in incoming) {
      message as? Frame.Text ?: continue
      val deserializedMessage: Message =
        messagesFormat.decodeFromStream(message.data.inputStream())
      println("${deserializedMessage.content} // ${(deserializedMessage as? SystemMessage)?.systemInfo}")
    }
  } catch (e: Exception) {
    println("Error while receiving: " + e.localizedMessage)
  }
}

private suspend fun DefaultClientWebSocketSession.sendMessage(message: Message) {
  val serializedMessage = messagesFormat.encodeToString(message)
  try {
    send(serializedMessage)
  } catch (e: Exception) {
    println("Some error occur: " + e.localizedMessage)
    return
  }
}</pre>



<p>Here&#8217;s our simple server that going to get our message and send it back with some changes:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">routing {
  webSocket("/hello") {
    try {
      for (frame in incoming) {
        frame as? Frame.Text ?: continue
        val deserializedMessage: WebSocketSession.Message =
          WebSocketSession.messagesFormat.decodeFromStream(frame.data.inputStream())
        val newMessageText = deserializedMessage.content + " - from Client"
        val serializedMessage = WebSocketSession.messagesFormat.encodeToString&lt;WebSocketSession.Message>(
          WebSocketSession.SystemMessage(
            content = newMessageText,
            systemInfo = "Important"
          )
        )
        Connection(this).session.send(serializedMessage)
      }
    } catch (e: Exception) {
      println(e.localizedMessage)
    }
  }
}</pre>



<p>The final part is to test it. We simply connect it to our already existing server:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun main() {
  embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
    configureUserRoutes()
    configureSerialization()
    configureSockets()
  }.start(wait = true)
}</pre>



<p>And now run the client, for example, I’ve created this one:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">suspend fun connectWebSocket() {
  client.webSocket(
    host = "0.0.0.0",
    port = 8080,
    path = "/hello"
  ) {
    launch { sendMessage(TextMessage("Good morning!")) }
    launch { receiveMessage() }
    delay(2000)
    launch { sendMessage(TextMessage("Hello!")) }
    launch { receiveMessage() }
    delay(2000)
    println("Connection closed. Goodbye!")
    client.close()
  }
}

runBlocking {
  WebSocketSession.connectWebSocket()
}</pre>



<p>Here’s the results:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">2023-12-01 15:46:26.104 [DefaultDispatcher-worker-4] TRACE io.ktor.websocket.WebSocket - Sending Frame TEXT (fin=true, buffer len = 41) from session io.ktor.websocket.DefaultWebSocketSessionImpl@2e8ab815
2023-12-01 15:46:26.110 [DefaultDispatcher-worker-6] TRACE io.ktor.websocket.WebSocket - WebSocketSession(StandaloneCoroutine{Active}@4ecfdc65) receiving frame Frame TEXT (fin=true, buffer len = 82)
Good morning! - from Client // Important
2023-12-01 15:46:28.094 [DefaultDispatcher-worker-6] TRACE io.ktor.websocket.WebSocket - Sending Frame TEXT (fin=true, buffer len = 34) from session io.ktor.websocket.DefaultWebSocketSessionImpl@2e8ab815
2023-12-01 15:46:28.097 [DefaultDispatcher-worker-3] TRACE io.ktor.websocket.WebSocket - WebSocketSession(StandaloneCoroutine{Active}@4ecfdc65) receiving frame Frame TEXT (fin=true, buffer len = 75)
Hello! - from Client // Important
Connection closed. Goodbye!</pre>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this article about <strong>kotlinx.serialization with Ktor</strong>. </p>



<p>If you would like to download the full source code, then you can find it in <a href="https://github.com/codersee-blog/kotlin-ktor-kotlinx-serialization" target="_blank" rel="noreferrer noopener">this GitHub repository</a>. </p>



<p>Lastly, thank you for being here, and happy to hear your feedback <strong>in the comments section below</strong>!</p>
<p>The post <a href="https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/">How To Use kotlinx.serialization with Ktor and Kotlin?</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/how-to-use-kotlinx-serialization-with-ktor-and-kotlin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Secure REST API with Ktor &#8211; Role-based access control (RBAC)</title>
		<link>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/</link>
					<comments>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 19 Dec 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[RBAC]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008380</guid>

					<description><![CDATA[<p>In this, 3rd article in a series, I will show you how to add role-based access control (RBAC / role-based security) to our Ktor project.</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/">Secure REST API with Ktor &#8211; Role-based access control (RBAC)</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to the 3rd article in our secure REST API with Ktor series, in which I will show you how to set up <strong>role-based access control (RBAC)</strong>, also known as role-based security. </p>



<p>If you haven&#8217;t seen my previous articles, then I highly encourage you to check them out: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/" target="_blank" rel="noreferrer noopener">Secure REST API with Ktor and JWT Access Tokens</a></li>



<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">Secure Ktor app with JWT refresh tokens. Refresh token flow</a>.</li>
</ul>



<p>Moreover, in this tutorial, we will build functionality based on the refresh token article, so I highly encourage you to fetch the code from <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">this repo</a> now. <strong>But don&#8217;t worry</strong>, if you are interested only in the RBAC part, then please navigate to the &#8220;<em>Custom Ktor Authorization Plugin</em>&#8221; paragraph.</p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FgvhXfzl34pk%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-exactly-will-we-do-today">What Exactly Will We Do Today? </h2>



<p>At the end of this tutorial, you will know precisely how to secure your Ktor REST API with role-based access control (RBAC). </p>



<p>But to be more specific, we will work with two endpoints: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="648" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-1024x648.png" alt="Image shows two endpoint - get all users and get user by ID which will be secured with RBAC" class="wp-image-9008393" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-1024x648.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-300x190.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-768x486.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement.png 1252w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The <code>GET /api/user/{id}</code> will be accessible for users with either <code>ADMIN</code> or <code>USER</code> roles. The second one, <code>GET /api/user</code>, will be accessible only for <code>ADMIN</code> users. </p>



<p>And how we will achieve that? </p>



<p>Well, we will implement a <strong>custom Ktor authorization plugin</strong>, which will be triggered on the <strong>AuthenticationChecked hook</strong>. </p>



<p>And I&#8217;ll show you how to do it the easy way, without unnecessary logic 🙂 </p>



<h2 class="wp-block-heading" id="h-what-is-role-based-access-control-rbac">What Is Role-Based Access Control (RBAC)? </h2>



<p>Again, before we learn how to implement role-based access control (RBAC) in Ktor, let&#8217;s learn a bit about it. </p>



<p><strong>Role-based access control</strong> (RBAC) or role-based security is nothing else than a way of restricting system access to authorized users. </p>



<p>To put it simply, we define a set of <strong>roles</strong> in our system, which reflects the needs of given permissions from our organization. An example of such a role can be a <em>user, manager, admin</em>, and many many more. And based on this role we allow, or deny access for a particular, authenticated user. </p>



<p>When dealing with JWT tokens in a REST API, such information can be first stored inside the token, so that later, we can easily read a claim and check whether a request should be served, or not. </p>



<h2 class="wp-block-heading" id="h-update-user-class-and-repo">Update User Class and Repo</h2>



<p>With all of that said, let&#8217;s finally start the practice part. </p>



<p>As the first step, let&#8217;s navigate to the <code>User</code> class and insert a new field- <code>role</code>:  </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.UUID

data class User(
  val id: UUID,
  val username: String,
  val password: String,
  val role: String
)</pre>



<p>For the sake of simplicity, let&#8217;s leave it as a String value, but depending on your needs, you may want to consider using an <strong>enum</strong>, or even a <strong>sealed class</strong> instead.</p>



<p>At this point, our code won&#8217;t compile, so let&#8217;s navigate to the <code>UserRoute.kt</code> and update the <code>.toModel()</code> extension function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password,
    role = "USER"
  )</pre>



<p>As we can see, by default, all created users will have the <code>USER</code> role assigned. </p>



<p>Lastly, let&#8217;s navigate to the <code>UserRepository</code> and add some <code>ADMIN</code> user:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class UserRepository {

  private val users = mutableListOf(
    User(UUID.randomUUID(), "admin", "password", "ADMIN")
  )

  // the rest of the class
}</pre>



<h2 class="wp-block-heading" id="h-add-role-claim-to-jwt-token">Add Role Claim to JWT Token</h2>



<p>As the next step, let&#8217;s open up the <code>JwtService</code> and update these 3 functions: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun createAccessToken(username: String, role: String): String =
  createJwtToken(username, role, 3_600_000)

fun createRefreshToken(username: String, role: String): String =
  createJwtToken(username, role, 86_400_000)

private fun createJwtToken(
  username: String,
  role: String, 
  expireIn: Int,
): String =
  JWT.create()
    .withAudience(audience)
    .withIssuer(issuer)
    .withClaim("username", username)
    .withClaim("role", role)
    .withExpiresAt(Date(System.currentTimeMillis() + expireIn))
    .sign(Algorithm.HMAC256(secret))</pre>



<p>As I mentioned in the &#8220;theory&#8221; part- when dealing with JWT tokens, we can put information about the user&#8217;s role inside the token. </p>



<p>And that&#8217;s exactly what is happening here. From now on, every access and refresh token will contain an additional claim- the <code>role</code>&#8211; which will be populated with a role assigned to a user. </p>



<h2 class="wp-block-heading" id="h-update-userservice">Update UserService </h2>



<p>With that done, we must navigate to the <code>UserService</code> and update <code>authenticate</code> and <code>refreshToken</code> functions: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun authenticate(loginRequest: LoginRequest): AuthResponse? {
  val username = loginRequest.username
  val foundUser: User? = userRepository.findByUsername(username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
    val accessToken = jwtService.createAccessToken(username, foundUser.role)
    val refreshToken = jwtService.createRefreshToken(username, foundUser.role)

    refreshTokenRepository.save(refreshToken, username)

    AuthResponse(
      accessToken = accessToken,
      refreshToken = refreshToken,
    )
  } else
    null
}

fun refreshToken(token: String): String? {
  val decodedRefreshToken = verifyRefreshToken(token)
  val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

  return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
    val foundUser: User? = userRepository.findByUsername(persistedUsername)
    val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

    if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
      jwtService.createAccessToken(persistedUsername, foundUser.role)
    else
      null
  } else
    null
}</pre>



<p>From now on we must pass the user role to the <code>JwtService</code> when creating new tokens and that&#8217;s exactly what we updated here. </p>



<h2 class="wp-block-heading" id="h-custom-ktor-authorization-plugin">Custom Ktor Authorization Plugin</h2>



<h3 class="wp-block-heading" id="h-what-are-ktor-plugins">What Are Ktor Plugins? </h3>



<p>Before we implement our custom one, let&#8217;s understand <strong>what exactly Ktor plugins are</strong>.</p>



<p>Basically, plugins in Ktor are a way to implement a common functionality that is out of the scope of the application logic. But what do we mean by that? Well, things like serialization, deserialization, compression, encoding, etc.</p>



<p>And when we take a look at the following diagram:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="612" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-1024x612.png" alt="" class="wp-image-9008395" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-1024x612.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-300x179.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-768x459.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram.png 1468w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>We will clearly see that plugins are everything that sits between request, application handler, and response. And yes, routing is a plugin too. </p>



<p>At this point, we can see that a custom plugin will be a great wait to achieve RBAC in our Ktor application. Moreover, <strong>we will use a new simplified API for creating custom plugins introduced in Ktor v2.0.0</strong>.</p>



<h3 class="wp-block-heading" id="h-implement-ktor-rbac-plugin">Implement Ktor RBAC Plugin</h3>



<p>With all of that said, let&#8217;s navigate to the <code>plugins</code> package and create the <code>RoleBasedAuthorization.kt</code> and write the following: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*

class PluginConfiguration {
  var roles: Set&lt;String> = emptySet()
}

val RoleBasedAuthorizationPlugin = createRouteScopedPlugin(
  name = "RbacPlugin",
  createConfiguration = ::PluginConfiguration
) {
  val roles = pluginConfig.roles

  pluginConfig.apply {

    on(AuthenticationChecked) { call ->
      val tokenRole = getRoleFromToken(call)

      val authorized = roles.contains(tokenRole)

      if (!authorized) {
        println("User does not have any of the following roles: $roles")
        call.respond(HttpStatusCode.Forbidden)
      }
    }
  }
}

private fun getRoleFromToken(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("role")
    ?.asString()</pre>



<p>Firstly, we create the <code>PluginConfiguration</code> class with a <code>roles</code> field. This way, we will be able to configure later, which roles should be allowed for the given endpoint and which should be forbidden. </p>



<p>Later, we introduce our new, custom Ktor plugin using the <code>createRouteScopedPlugin</code> function. We must do that if we want to introduce a plugin that can be installed <strong>for a specific route</strong>. </p>



<p>Inside this function, we refer to  <code>pluginConfig</code> and configure what exactly our custom authorization plugin must do. Firstly, we set the <code>on</code> handler that accepts a <code>Hook</code> as a parameter- in our case the <code>AuthenticationChecked</code> is a hook. As per the <a href="https://ktor.io/docs/custom-plugins.html#other" target="_blank" rel="noreferrer noopener">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>AuthenticationChecked is executed after authentication credentials are checked. </cite></blockquote>



<p>Which simply means, that <strong>our plugin will be invoked after we authenticate the user successfully.</strong> With the old API, we would have to use the after <code>Authentication.ChallengePhase</code>.</p>



<p>Following, we <strong>extract the user role from the JWT <code>role</code> claim</strong> and if it is inside the Set with allowed roles, then we do nothing. Otherwise, we return <strong>403 Forbidden</strong>. </p>



<h2 class="wp-block-heading" id="h-add-roleutil">Add RoleUtil</h2>



<p>With that done, let&#8217;s navigate to the <code>util</code> package and introduce the <code>authorized</code> function inside the <code>RouteUtil.kt</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.RoleBasedAuthorizationPlugin
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Route.authorized(
  vararg hasAnyRole: String,
  build: Route.() -> Unit
) {
  install(RoleBasedAuthorizationPlugin) { roles = hasAnyRole.toSet() }
  build()
}</pre>



<p>To put it simply, we will use this function whenever we would like to <strong>authorize </strong>our request using role-based access control. </p>



<p>As a result, it will <strong>register our authorization plugin for a particular route.</strong></p>



<h2 class="wp-block-heading" id="h-update-user-routes">Update User Routes</h2>



<p>And with that said, let&#8217;s see it in action:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">authenticate {
  authorized("ADMIN") {

    get {
      val users = userService.findAll()

      call.respond(
        message = users.map(User::toResponse)
      )
    }

  }
}

authenticate("another-auth") {
  authorized("ADMIN", "USER") {

    get("/{id}") {
      val id: String = call.parameters["id"]
        ?: return@get call.respond(HttpStatusCode.BadRequest)

      val foundUser = userService.findById(id)
        ?: return@get call.respond(HttpStatusCode.NotFound)

      if (foundUser.username != extractPrincipalUsername(call))
        return@get call.respond(HttpStatusCode.NotFound)

      call.respond(
        message = foundUser.toResponse()
      )
    }
  }</pre>



<p>As we can see, from now on we can simply use the <code>authorized</code> whenever we want to limit endpoint accessibility to particular roles. </p>



<p>Of course, we must remember that this can be done only when the user is <strong>authenticated</strong>, because otherwise, we won&#8217;t be able to read the <code>role</code> claim. </p>



<h2 class="wp-block-heading" id="h-ktor-rbac-summary">Ktor RBAC summary</h2>



<p>And that&#8217;s all for this article on how to implement role-based access control (RBAC) in Ktor. </p>



<p>I hope this article (and a series) helped you to learn how easily we can set up different things related to security in Ktor. If you haven&#8217;t seen previous materials yet, then you can do that right here: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a></li>



<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a></li>
</ul>



<p>As always, you can find a source code in <a href="https://github.com/codersee-blog/kotlin-ktor-custom-authorization-plugin-rbac-jwt/tree/main" target="_blank" rel="noreferrer noopener">this GitHub repository</a> and <strong>let me know your thoughts</strong> in the comments section below 🙂</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/">Secure REST API with Ktor &#8211; Role-based access control (RBAC)</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Secure Ktor app with JWT refresh tokens. Refresh token flow.</title>
		<link>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/</link>
					<comments>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 28 Nov 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Resfresh Tokens]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008372</guid>

					<description><![CDATA[<p>In the second article of my secure Ktor series, I will show you how to implement a JWT refresh token flow functionality.</p>
<p>The post <a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In the second article of my secure <strong>Ktor </strong>series, I will show you how to implement a <strong>JWT refresh token flow</strong> functionality.</p>



<p>To not duplicate ourselves, we will use the project implemented in the previous blog post, in which we learned how to implement a secure <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/" target="_blank" rel="noreferrer noopener">REST API with Ktor and authenticate with JWT access tokens</a>. So if you haven&#8217;t read that article yet, I highly encourage you to do it now. Nevertheless, if you are interested more in the <strong>refresh token part</strong>, you can find a link to the GitHub repository and simply clone the project. </p>



<p>Without any further ado, let&#8217;s get to work 🙂 </p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F97_u55vGQz0%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-a-tiny-bit-of-theory">A Tiny Bit Of Theory</h2>



<p>If you&#8217;re following me for a longer period, you know that I focus on the practical side of different problems. </p>



<p>Nevertheless, I owe you a bit of explanation before we learn how to secure our Ktor app with JWT refresh token flow. So, in the following paragraphs, I will shortly describe: </p>



<ul class="wp-block-list">
<li>the difference between access tokens and refresh tokens,</li>



<li>what is a refresh token flow, </li>



<li>and advantages of such a flow. </li>
</ul>



<p>Of course, if you feel that&#8217;s not necessary in your case, then feel free to skip to the <em>Implement RefreshTokenRepository</em> section 😉 </p>



<h3 class="wp-block-heading" id="h-access-tokens-vs-refresh-tokens">Access Tokens vs Refresh Tokens</h3>



<p>Let&#8217;s start everything by distinguishing these two. </p>



<p>First of all, they both play distinct roles in token-based authentication systems. </p>



<p>An <strong>access token</strong> is a <strong>short-lived</strong> credential that is issued to a client after a successful authentication. Its purpose is to grant the client permission to access protected resources on behalf of the user. And by the client, we mean here a web app, a mobile app, or any other integration working with our REST API. </p>



<p>They are designed to have a limited lifespan to enhance security, reducing the risk associated with compromised tokens. </p>



<p><strong>Refresh tokens</strong>, on the other hand, are l<strong>ong-lived</strong> credentials that are used to obtain new access tokens without requiring the user to re-authenticate. </p>



<p>While access tokens are meant for short-term authorization, refresh tokens provide a mechanism for obtaining fresh access tokens and extending the user&#8217;s session securely. </p>



<h3 class="wp-block-heading" id="h-refresh-token-flow">Refresh Token Flow</h3>



<p>To put it simply, when a client authenticates successfully, it receives two tokens: an access token, and a refresh token. </p>



<p>The access token will be used to query our API. If it is compromised, the bad actor will have a limited time to perform unwanted actions on behalf of the user (thanks to its short-lived nature). </p>



<p>The refresh token, with a longer expiration time, will allow the client to get a new access token whenever it expires. This way, the user does not need to specify credentials (like username, and password for instance) every time an access token expires. </p>



<p>With this flow, we can strike a balance between usability and security. We improve user experience and maintain protection against unauthorized access.</p>



<h3 class="wp-block-heading" id="h-pros-vs-cons">Pros vs Cons </h3>



<p>As with every solution, refresh token flow has both pros and cons. </p>



<p>When it comes to the advantages, we already covered the first two- the shorter lifespan of access tokens <strong>limits the potential impact of a token compromise</strong>. Additionally, a long-living refresh token means a <strong>better user experience</strong>&#8211; a longer time between asking the user for his credentials. Lastly, this flow gives us <strong>more granular control</strong> over token management, for example, we can revoke refresh tokens selectively. </p>



<p>When it comes to the disadvantages, we could call them <strong>additional complexity</strong>. Not only we must carefully implement on the backend to prevent any vulnerabilities, but also, the storage and transmission of refresh tokens need to be handled securely to prevent unauthorized access.</p>



<p>Lastly, should we always use refresh token flow in our applications? </p>



<p>The answer is- as always- <strong>it depends</strong>. When dealing with security for our app we should spend some time investigating in depth all options on the table. Such an analysis would be way too much for the article about implementing a Ktor JWT refresh token flow 😉 </p>



<h2 class="wp-block-heading" id="h-implement-refreshtokenrepository">Implement RefreshTokenRepository</h2>



<p>With all of that said, we can finally start the practice part. </p>



<p>As the first step, let&#8217;s navigate to the <code>repository</code> package in our Ktor project and add the <code>RefreshTokenRepository</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class RefreshTokenRepository {

  private val tokens = mutableMapOf&lt;String, String>()

  fun findUsernameByToken(token: String): String? =
    tokens[token]

  fun save(token: String, username: String) {
    tokens[token] = username
  }

}</pre>



<p>Again, in a real-life scenario, this would be the class responsible for communicating to the data source, like a relational database, MongoDB, or any other source used in the project. To keep our tutorial simple and focused on the Ktor/JWT combination, we will use a mutable map storing a combination of tokens with usernames in memory. </p>



<p>As we can see, this class exposes two functions. One is for persisting the data in our map, and the second one is to retrieve usernames based on the JWT refresh token value. </p>



<h2 class="wp-block-heading" id="h-update-jwtservice">Update JwtService </h2>



<p>Nextly, let&#8217;s open up the <code>JwtService</code> class and update it a bit: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import com.codersee.repository.UserRepository
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userRepository: UserRepository,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  fun createAccessToken(username: String): String =
    createJwtToken(username, 3_600_000)

  fun createRefreshToken(username: String): String =
    createJwtToken(username, 86_400_000)

  private fun createJwtToken(username: String, expireIn: Int): String =
    JWT.create()
      .withAudience(audience)
      .withIssuer(issuer)
      .withClaim("username", username)
      .withExpiresAt(Date(System.currentTimeMillis() + expireIn))
      .sign(Algorithm.HMAC256(secret))


  fun customValidator(
    credential: JWTCredential,
  ): JWTPrincipal? {
    val username: String? = extractUsername(credential)
    val foundUser: User? = username?.let(userRepository::findByUsername)

    return foundUser?.let {
      if (audienceMatches(credential))
        JWTPrincipal(credential.payload)
      else
        null
    }
  }

  private fun audienceMatches(
    credential: JWTCredential,
  ): Boolean =
    credential.payload.audience.contains(audience)

  fun audienceMatches(
    audience: String
  ): Boolean =
    this.audience == audience

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()

  private fun extractUsername(credential: JWTCredential): String? =
    credential.payload.getClaim("username").asString()
}</pre>



<p>First of all, we replaced the <code>UserService</code> dependency from the constructor with the <code>UserRepository</code>. This way, we would avoid a circular dependency between this class and <code>UserService</code> later. </p>



<p>Then, we extracted the code responsible for generating JWT tokens into a separate function- <code>createJwtToken</code>&#8211; which will be called by the <code>createAccessToken</code>, and <code>createRefreshToken</code>. Please note that these two functions declare different expiration times for both token types- 1 hour for the access token and 24 for the refresh token. </p>



<p>But what expiration time should we set in a real-life? Well, again, it depends 🙂 And we must consider both security concerns, as well as the <strong>storage</strong>. Yes, the longer the expiration time, the bigger the amount of refresh tokens stored in our database.  </p>



<p>Lastly, we added the <code>audienceMatches</code> function, which we will use later in the code. </p>



<h2 class="wp-block-heading" id="h-update-routing-layer">Update Routing Layer</h2>



<p>With all of that done, let&#8217;s navigate to the <code>routing</code> package and make the necessary changes too. </p>



<h3 class="wp-block-heading" id="h-add-request-response-classes">Add Request/Response Classes </h3>



<p>As the first step, let&#8217;s add a class responsible for mapping refresh token call JSON payload:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenRequest(
  val token: String,
)</pre>



<p>Following, let&#8217;s implement a class, which will serialize the authentication response:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class AuthResponse(
  val accessToken: String,
  val refreshToken: String,
)</pre>



<p>And finally, the <code>RefreshTokenResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenResponse(
  val token: String,
)</pre>



<h3 class="wp-block-heading" id="h-update-routing-kt">Update Routing.kt</h3>



<p>As the next step, let&#8217;s update the <code>Routing.kt</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  userService: UserService
) {
  routing {

    route("/api/auth") {
      authRoute(userService)
    }

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>As we can see, we don&#8217;t inject the <code>JwtService</code> anymore. Instead, we invoke the <code>authRoute</code> with <code>UserService</code> instance instead. </p>



<p><h3 class="wp-block-heading" id="h-update-routing-kt">Edit AuthRoute.kt</h3>With that done, let&#8217;s update the <code>AuthRoute</code> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.routing.request.LoginRequest
import com.codersee.routing.request.RefreshTokenRequest
import com.codersee.routing.response.AuthResponse
import com.codersee.routing.response.RefreshTokenResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Route.authRoute(userService: UserService) {

  post {
    val loginRequest = call.receive&lt;LoginRequest>()

    val authResponse: AuthResponse? = userService.authenticate(loginRequest)

    authResponse?.let {
      call.respond(authResponse)
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

  post("/refresh") {
    val request = call.receive&lt;RefreshTokenRequest>()

    val newAccessToken = userService.refreshToken(token = request.token)

    newAccessToken?.let {
      call.respond(
        RefreshTokenResponse(it)
      )
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

}</pre>



<p>So, instead of dealing with <code>JwtService</code>, we are going to invoke the <code>UserService</code> functions instead. </p>



<p>In the first handler for <code>POST /api/auth</code> calls, we invoke the <code>authenticate</code> method and if we receive an <code>AuthResponse</code>, then we will simply return that to the API client with <code>200 OK</code> status. Otherwise, we will return <code>401 Unauthorized</code>. </p>



<p>Additionally, we add a new handler for <code>POST /api/auth/refresh</code> calls. Inside it, we read the JSON payload and create an instance of <code>RefreshTokenRequest</code>. Then, we invoke the <code>refreshToken</code> method from <code>UserService</code> (which we are going to implement in a moment). And just like previously, if the service returns a non-null object, then we respond with <code>200 OK</code> and with <code>401 Unauthorized</code> if that&#8217;s not the case. </p>



<h2 class="wp-block-heading" id="h-update-userservice">Update UserService</h2>



<p>As the last thing in our Ktor with the JWT refresh token tutorial, we must update the <code>UserService</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.interfaces.DecodedJWT
import com.codersee.model.User
import com.codersee.repository.RefreshTokenRepository
import com.codersee.repository.UserRepository
import com.codersee.routing.request.LoginRequest
import com.codersee.routing.response.AuthResponse
import java.util.*

class UserService(
  private val userRepository: UserRepository,
  private val jwtService: JwtService,
  private val refreshTokenRepository: RefreshTokenRepository
) {

  fun findAll(): List&lt;User> =
    userRepository.findAll()

  fun findById(id: String): User? =
    userRepository.findById(
      id = UUID.fromString(id)
    )

  fun findByUsername(username: String): User? =
    userRepository.findByUsername(username)

  fun save(user: User): User? {
    val foundUser = userRepository.findByUsername(user.username)

    return if (foundUser == null) {
      userRepository.save(user)
      user
    } else null
  }

  fun authenticate(loginRequest: LoginRequest): AuthResponse? {
    val username = loginRequest.username
    val foundUser: User? = userRepository.findByUsername(username)

    return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
      val accessToken = jwtService.createAccessToken(username)
      val refreshToken = jwtService.createRefreshToken(username)

      refreshTokenRepository.save(refreshToken, username)

      AuthResponse(
        accessToken = accessToken,
        refreshToken = refreshToken,
      )
    } else
      null
  }

  fun refreshToken(token: String): String? {
    val decodedRefreshToken = verifyRefreshToken(token)
    val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

    return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
      val foundUser: User? = userRepository.findByUsername(persistedUsername)
      val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

      if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
        jwtService.createAccessToken(persistedUsername)
      else
        null
    } else
      null
  }

  private fun verifyRefreshToken(token: String): DecodedJWT? {
    val decodedJwt: DecodedJWT? = getDecodedJwt(token)

    return decodedJwt?.let {
      val audienceMatches = jwtService.audienceMatches(it.audience.first())

      if (audienceMatches)
        decodedJwt
      else
        null
    }
  }

  private fun getDecodedJwt(token: String): DecodedJWT? =
    try {
      jwtService.jwtVerifier.verify(token)
    } catch (ex: Exception) {
      null
    }
}</pre>



<p>And let&#8217;s see the most important changes here. </p>



<p>Firstly, we add the <code>authenticate</code> function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun authenticate(loginRequest: LoginRequest): AuthResponse? {
  val username = loginRequest.username
  val foundUser: User? = userRepository.findByUsername(username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
    val accessToken = jwtService.createAccessToken(username)
    val refreshToken = jwtService.createRefreshToken(username)

    refreshTokenRepository.save(refreshToken, username)

    AuthResponse(
      accessToken = accessToken,
      refreshToken = refreshToken,
    )
  } else
    null
}</pre>



<p>This one is simply responsible for fetching a user from our repository using the passed <code>username</code>. </p>



<p></p>



<p>If we find it successfully, we check if the password matches (again, it&#8217;s just a dummy plain text comparison and you want to learn more about password checks in real-life scenarios). And if that&#8217;s the case, then we simply return the access and refresh tokens to the user and persist the refresh token in our &#8220;database&#8221;. </p>



<p>Nextly, we have a refresh token flow function: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun refreshToken(token: String): String? {
  val decodedRefreshToken = verifyRefreshToken(token)
  val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

  return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
    val foundUser: User? = userRepository.findByUsername(persistedUsername)
    val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

    if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
      jwtService.createAccessToken(persistedUsername)
    else
      null
  } else
    null
}</pre>



<p>When it comes to refreshing a token, we check if there is any persisted username found by the token value. If it is, then we fetch the user by the given username (this way, we won&#8217;t refresh a token for user that does not exist anymore). </p>



<p>And at this point, we can <strong>rerun </strong>our application and <strong>verify </strong>if our logic works, as expected. Again, if you&#8217;d like to get a ready-to-go Postman collection, then please let me know <strong>in the comments section</strong>.</p>



<h2 class="wp-block-heading" id="h-ktor-with-jwt-refresh-token-summary">Ktor With JWT Refresh Token Summary</h2>



<p>And basically, that&#8217;s all for this, second tutorial in a series related to Ktor security, in which we learned how to create a <strong>Ktor application with JWT refresh token flow</strong>. </p>



<p>As always, you can find the whole source code in my <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">GitHub repository</a>. </p>



<p>Thank you for being here and please do not forget to share it with other people on your social media (it will help me a lot) and check other posts from the series:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a>, </li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>
<p>The post <a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Secure REST API with Ktor and JWT Access Tokens</title>
		<link>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/</link>
					<comments>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 21 Nov 2023 08:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008343</guid>

					<description><![CDATA[<p>In this, first article in a series, I will show you how to create a REST API with Ktor and secure it with JWT (JSON Web Token) access tokens.</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to the <strong>first article in a series</strong>. This time, I will show you how to implement a simple <strong>REST API with Ktor</strong> and secure it with <strong>JWT (JSON Web Token) access tokens</strong>.</p>



<p>To be even more specific, in this tutorial I will teach you:</p>



<ul class="wp-block-list">
<li>how to implement a simple <strong>REST API</strong>, </li>



<li>what are <strong>JSON Web Tokens</strong> and what does the <strong>authentication</strong> mean, </li>



<li>how to <strong>generate </strong>and <strong>validate JWT access tokens</strong>, and <strong>authenticate </strong>users, </li>



<li>and how to read data using the <strong>JWTPrincipal</strong>.</li>
</ul>



<p>In the upcoming articles, we will continue the topic and learn additionally: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">how to implement a <strong>refresh token flow </strong>with Ktor</a>,</li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FWmURqVIWZeQ%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-is-authentication">What Is Authentication? </h2>



<p>But before we start implementing our Ktor app with JWT tokens, let me quickly explain <strong>what exactly authentication is.</strong> </p>



<p>In essence, it is the process of <strong>verifying the identity</strong> of a user, system, or device <strong>to ensure that they are who they claim to be</strong>. </p>



<p>It is like having a secret code to enter a club. Let&#8217;s imagine we want to get into a cool party, and at the entrance, there&#8217;s a bouncer checking everyone&#8217;s membership. To prove we belong, we provide a special password. If the password matches what&#8217;s on the list, we&#8217;re in! </p>



<p>In the digital world, it&#8217;s the same idea &#8211; we need to confirm our identity with a unique key (like a password or JWT token) to access our email, bank account, or, like in our case, any resource through the exposed REST API. </p>



<p>Of course, <strong>authentication<em> </em></strong>is not a synonym for <strong>authorization</strong> (but we will get back to that in the upcoming post). </p>



<h2 class="wp-block-heading" id="h-jwt-tokens-explained">JWT Tokens Explained</h2>



<p>As the last thing before we head to the practice, let&#8217;s learn a bit about the JWT tokens. </p>



<p><strong>JWTs</strong>, also known as <strong>JSON Web Tokens</strong>, are nothing else than one of plenty of existing ways to authenticate users. </p>



<p>Just like with our previous example- in real life, we can use our ID, passport, or in other cases, some secret word. </p>



<p>Here, the JWT is like a digital secret handshake on the internet. When sign in using our username and password, the server gives us a special JWT, and later, we can use it to access a REST API. This way, we don&#8217;t need to specify such vulnerable data every time we want to make a request. </p>



<p>What are the other advantages? Well, they are <strong>safe and compact, </strong>and allow us to <strong>hold information</strong>. </p>



<p>JWTs consist of three parts separated by dots: header.payload.signature: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="534" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png" alt="The image is a screenshot from jwt.io page and present and example encoded JWT token with its decoded value." class="wp-image-9008239" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-300x156.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-768x400.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io.png 1191w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>The encoded value can be easily decoded and JWT token values can be read. </p>



<p>The <strong>header </strong>typically consists of two parts: the type of the token (missing above), and the signing algorithm being used (HMAC SHA512).</p>



<p>The second part of the token is the payload, which contains the <strong>claims</strong>. Claims, Claims are statements about an entity (typically, the user) and additional data.</p>



<p>Lastly, the <strong>signature</strong> is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn&#8217;t changed along the way.</p>



<h2 class="wp-block-heading" id="h-set-up-new-ktor-project">Set Up New Ktor Project</h2>



<p>With all of that said, let&#8217;s navigate to the <a href="https://start.ktor.io/" target="_blank" rel="noreferrer noopener">Ktor Project Generator</a> and start by specifying the necessary details: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="731" height="851" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1.png" alt="Image is a screenshot from the Ktor Project Generator page and shows the first step of generator settings, like Ktor version, engine set to Netty, or HOCON file config. " class="wp-image-9008354" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1.png 731w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1-258x300.png 258w" sizes="auto, (max-width: 731px) 100vw, 731px" /></figure>



<p>At this stage, we must specify the basic things, like the Ktor version, engine, etc. </p>



<p>Nextly, let&#8217;s click the &#8220;Add plugins&#8221; button and configure the following plugins (aka dependencies):</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="630" height="750" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2.png" alt="Image shows all added plugins in the configurator: authentication, authentication JWT, content negotiation, kotlinx.serialization, and routing." class="wp-image-9008355" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2.png 630w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2-252x300.png 252w" sizes="auto, (max-width: 630px) 100vw, 630px" /></figure>



<p>As we can see, in order to expose a REST API and secure it with JWT in Ktor, we will need the following: </p>



<ul class="wp-block-list">
<li>Authentication,</li>



<li>Authentication JWT, </li>



<li>Content Negotiation,</li>



<li>kotlinx.serialization,</li>



<li>and Routing. </li>
</ul>



<p>With that done, let&#8217;s generate the project, download the ZIP file, extract it, and import it to the IntelliJ IDEA. </p>



<p>When we open up the project, we should see that there are some plugins already configured inside the <code>plugins</code> package and <code>Application.kt</code> file. </p>



<p>Let&#8217;s delete files inside the <code>plugins</code> and clean up the <code>Application.kt</code> to be on the same page:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
}</pre>



<h2 class="wp-block-heading" id="h-expose-users-api">Expose Users API </h2>



<p>As the next step, let&#8217;s expose the API responsible for <strong>user management</strong>.</p>



<h3 class="wp-block-heading" id="h-add-user-model">Add User Model</h3>



<p>Firstly, let&#8217;s create the <code>model</code> package and implement the <code>User.kt</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.UUID

data class User(
  val id: UUID,
  val username: String,
  val password: String
)</pre>



<p>As can be seen, it&#8217;s just a simple class with the <em>id, username, </em>and <em>password </em>fields.</p>



<h3 class="wp-block-heading" id="h-create-userrepository">Create UserRepository</h3>



<p>Secondly, we must add a class responsible for user storage and retrieval. </p>



<p>To do so, let&#8217;s add the <code>repository</code> package and put the <code>UserRepository</code> inside it:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import java.util.*

class UserRepository {

  private val users = mutableListOf&lt;User>()

  fun findAll(): List&lt;User> =
    users

  fun findById(id: UUID): User? =
    users.firstOrNull { it.id == id }

  fun findByUsername(username: String): User? =
    users.firstOrNull { it.username == username }

  fun save(user: User): Boolean =
    users.add(user)
}</pre>



<p>Normally, we would put a code responsible for &#8220;talking&#8221; to the database right here. </p>



<p>In our case, to not lose focus from the clue of this article- authentication in Ktor and JWT- we introduce a dummy repository, which uses the mutable list to store users in memory. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Note:</strong> in our dummy repository, we store user password in a plain text. <br><br>Nevertheless, in real-life scenarios we should always encrypt it before persisting. To learn a bit more about different encryption algorithms, you can check out my other post in which I explain <a href="https://blog.codersee.com/kotlin-pbkdf2-secure-password-hashing/">PBKDF2 hashing</a>.</cite></blockquote>



<h3 class="wp-block-heading" id="h-implement-userservice">Implement UserService</h3>



<p>With that done, let&#8217;s introduce a service layer in our project. </p>



<p>So, similarly, let&#8217;s add the <code>service</code> package and the <code>UserService</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.repository.UserRepository
import java.util.*

class UserService(
  private val userRepository: UserRepository
) {

  fun findAll(): List&lt;User> =
    userRepository.findAll()

  fun findById(id: String): User? =
    userRepository.findById(
      id = UUID.fromString(id)
    )

  fun findByUsername(username: String): User? =
    userRepository.findByUsername(username)

  fun save(user: User): User? {
    val foundUser = userRepository.findByUsername(user.username)

    return if (foundUser == null) {
      userRepository.save(user)
      user
    } else null
  }
}</pre>



<p>This time, we expose 4 functions, which we will utilize later when handling requests and working with JWT tokens. </p>



<h3 class="wp-block-heading" id="h-add-request-and-response-classes">Add Request and Response Classes</h3>



<p>Before we expose our REST endpoint, let&#8217;s take care of the request and response classes. </p>



<p>Let&#8217;s start by creating the <code>routing.request</code> package and implementing the <code>UserRequest</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class UserRequest(
  val username: String,
  val password: String,
)</pre>



<p>As we can see, we must annotate objects that we want to <strong>serialize</strong> or <strong>deserialize</strong> using the <strong>@Serializable</strong> when working with <code>kotlinx.serialization</code> library. </p>



<p>As a result, the serialization plugin will automatically generate the implementation of <em>KSerializer</em>. </p>



<p>Similarly, let&#8217;s add the <code>routing.response</code> package and the <code>UserResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class UserResponse(
  val id: UUID,
  val username: String,
)</pre>



<p>This one seems pretty similar, but <strong>unfortunately</strong>, we must do one more thing here.</p>



<h3 class="wp-block-heading" id="h-custom-uuid-kserializer">Custom UUID KSerializer</h3>



<p>If we would try to use this class as it is, we would end up with <strong>the following error:</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Serializer has not been found for type &#8216;UUID&#8217;. To use context serializer as fallback, explicitly annotate type or property with @Contextual.</cite></blockquote>



<p>And to fix this issue, <strong>we must implement our own UUID serializer</strong>.</p>



<p>So as the first step, let&#8217;s introduce the <code>util</code> package and add the <code>UUIDSerializer</code> object: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.*

object UUIDSerializer : KSerializer&lt;UUID> {

  override val descriptor: SerialDescriptor
    get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)

  override fun deserialize(decoder: Decoder): UUID =
    UUID.fromString(decoder.decodeString())

  override fun serialize(encoder: Encoder, value: UUID) =
    encoder.encodeString(value.toString())
}</pre>



<p>Following, let&#8217;s instruct the plugin to use our implementation whenever it wants to serialize the <code>id</code> field:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.util.UUIDSerializer
import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class UserResponse(
  @Serializable(with = UUIDSerializer::class)
  val id: UUID,
  val username: String,
)</pre>



<h3 class="wp-block-heading" id="h-register-serialization-plugin">Register Serialization Plugin</h3>



<p>When working with Ktor, we must explicitly specify the plugins we use. There is no component scan based on the dependencies which you may know from Spring Boot 🙂 </p>



<p>So as the next step, let&#8217;s add the <code>Seriaization.kt</code> to the <code>plugins</code> package: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*

fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json()
  }
}</pre>



<p>The <code>ContentNegotiation</code> plugin has two tasks: </p>



<ul class="wp-block-list">
<li>it negotiates the media types between the client and server, </li>



<li>and serializes/deserializes the content in a specific format. </li>
</ul>



<p>In our case, we can clearly see that we will be using the <code>JSON</code> format. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: other formats, like XML, CBOR, and ProtoBuf are supported, too. </cite></blockquote>



<p>Lastly, we must make use of our extension function inside the <code>Application.kt</code> file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSerialization
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  configureSerialization()
}</pre>



<h3 class="wp-block-heading" id="h-add-user-route">Add User Route</h3>



<p>With all of that being done, we can finally expose endpoints for user management. </p>



<p>As the first step, let&#8217;s implement the <code>Routing.kt</code> class inside the <code>routing</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  userService: UserService
) {
  routing {

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>This way, we instruct Ktor that whenever a request is made to the <code>/api/user**</code>, it should look for a handler inside the <code>userRoute</code> (which we will add in a moment). </p>



<p>Following, let&#8217;s get back to the <code>Application.kt</code> and register our routing: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSerialization
import com.codersee.repository.UserRepository
import com.codersee.routing.configureRouting
import com.codersee.service.UserService
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  val userRepository = UserRepository()
  val userService = UserService(userRepository)

  configureSerialization()
  configureRouting(userService)
}</pre>



<p>Nextly, let&#8217;s add the <code>UserRoute.kt</code> inside the <code>routing</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.routing.request.UserRequest
import com.codersee.routing.response.UserResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.*

fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

  get {
    val users = userService.findAll()

    call.respond(
      message = users.map(User::toResponse)
    )
  }

  get("/{id}") {
    val id: String = call.parameters["id"]
      ?: return@get call.respond(HttpStatusCode.BadRequest)

    val foundUser = userService.findById(id)
      ?: return@get call.respond(HttpStatusCode.NotFound)

    call.respond(
      message = foundUser.toResponse()
    )
  }
}

private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password
  )

private fun User.toResponse(): UserResponse =
  UserResponse(
    id = this.id,
    username = this.username,
  )</pre>



<p>And to be on the same page, let&#8217;s rephrase what is going on here:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.routing.request.UserRequest
import com.codersee.routing.response.UserResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.*

fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

}</pre>



<p>Firstly, we inject the instance of <code>UserService</code>, which we will use to save and retrieve users. </p>



<p>Nextly, we specify a handler for <strong>POST requests</strong>. </p>



<p>Inside this handler, we get the JSON payload sent by the API consumer as an instance of <em>UserRequest</em> with the <code>call.receive&lt;T&gt;()</code> function. Then, we invoke the <code>save</code> function and pass the instance of the <code>User</code> class, which we obtain using the <code>toModel()</code> extension function. If the <code>save</code> returns a <em>null</em> value, we return <code>400 Bad Request</code> with the <code>call.respond()</code> function. </p>



<p>On the other hand, if the user was saved successfully, we respond with a <code>201 Created</code> status and pass its identifier as the <code>id</code> header value with <code>call.response.header()</code> function. </p>



<p>Then, we have another handler responsible for <code>GET /api/user</code> requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">get {
  val users = userService.findAll()

  call.respond(
    message = users.map(User::toResponse)
  )
}</pre>



<p>It is responsible for fetching all users and returning them after mapping with <code>toResponse()</code> extension functions. </p>



<p>Lastly, we have the handler answering to the <code>GET /api/user/{id}</code> requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">get("/{id}") {
  val id: String = call.parameters["id"]
    ?: return@get call.respond(HttpStatusCode.BadRequest)

  val foundUser = userService.findById(id)
    ?: return@get call.respond(HttpStatusCode.NotFound)

  call.respond(
    message = foundUser.toResponse()
  )
}</pre>



<p>This time, we read the <strong>path variable</strong> <code>id</code> using the <code>call.paramaters</code>. If is not present, we return a <code>400 Bad Request</code> response. </p>



<p>Following, we try to find a user by ID and if we fail, then we simply return the <code>404 Not Found</code>. </p>



<h2 class="wp-block-heading" id="h-ktor-with-jwt">Ktor With JWT </h2>



<p>Excellent! At this point, we have a working API, so it&#8217;s finally time to learn a bit more about how to secure it, and how to generate JWT tokens in our Ktor application so that we can use them later to access these endpoints. </p>



<h3 class="wp-block-heading" id="h-update-config-file">Update Config File</h3>



<p>As the first step, let&#8217;s navigate to the <code>application.conf</code> file. </p>



<p>Among other settings, we should see the following <code>jwt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">jwt {
    domain = "https://jwt-provider-domain/"
    audience = "jwt-audience"
    realm = "ktor sample app"
}</pre>



<p>Let&#8217;s slightly modify it:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">jwt {
    audience = "my-audience"
    issuer = "http://localhost/"
    realm = "My realm"
    secret = ${SECRET}
}</pre>



<p>Without going into too much detail, these values (except for the <strong>secret</strong>) will be <strong>claims</strong> in our JWT tokens: </p>



<ul class="wp-block-list">
<li><strong>audience</strong>&#8211; which describes the recipient of the token, </li>



<li><strong>issuer</strong>&#8211; which is the entity that issues the token,</li>



<li><strong>realm</strong>&#8211; which is an optional parameter providing additional context or scope,</li>



<li><strong>secret</strong>&#8211; which is a secret key used for token signing and verification.</li>
</ul>



<p>Please note that the <code>secret</code> value <strong>IS NOT </strong>hardcoded (and should never be). We will source it from the environment variable called <code>SECRET</code>.</p>



<h2 class="wp-block-heading" id="h-implement-jwtservice">Implement JwtService</h2>



<p>With that done, we can navigate to the <code>service</code> package and add the <code>JwtService</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userService: UserService,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  fun createJwtToken(loginRequest: LoginRequest): String? {
    val foundUser: User? = userService.findByUsername(loginRequest.username)

    return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password)
      JWT.create()
        .withAudience(audience)
        .withIssuer(issuer)
        .withClaim("username", loginRequest.username)
        .withExpiresAt(Date(System.currentTimeMillis() + 3_600_000))
        .sign(Algorithm.HMAC256(secret))
    else
      null
  }

  fun customValidator(
    credential: JWTCredential,
  ): JWTPrincipal? {
    val username: String? = extractUsername(credential)
    val foundUser: User? = username?.let(userService::findByUsername)

    return foundUser?.let {
      if (audienceMatches(credential))
        JWTPrincipal(credential.payload)
      else
        null
    }
  }

  private fun audienceMatches(
    credential: JWTCredential,
  ): Boolean =
    credential.payload.audience.contains(audience)

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()

  private fun extractUsername(credential: JWTCredential): String? =
    credential.payload.getClaim("username").asString()
}
</pre>



<p>Again, let&#8217;s break it down into smaller chunks: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userService: UserService,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()
}</pre>



<p>Firstly, we inject the instance of <code>Application</code> and <code>UserService</code> classes. </p>



<p>Following we read the configuration properties we set in the previous step. 3 of them can be private, but the <code>realm</code> will be later used in another class to configure security. </p>



<p>After that, we must create an instance of the <code>JwtVerifier</code> and configure it. In simple words, this class holds the verify method which will later verify that a given token has not only a proper JWT format but also its signature matches. </p>



<p>And this is why we specify which <strong>algorithm</strong>, <strong>audience</strong>, and <strong>issuer</strong> a valid token must have.</p>



<p>With all of that done, we introduce the <em>createJwtToken</em> function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun createJwtToken(loginRequest: LoginRequest): String? {
  val foundUser: User? = userService.findByUsername(loginRequest.username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password)
    JWT.create()
      .withAudience(audience)
      .withIssuer(issuer)
      .withClaim("username", loginRequest.username)
      .withExpiresAt(Date(System.currentTimeMillis() + 3_600_000))
      .sign(Algorithm.HMAC256(secret))
  else
    null
}</pre>



<p>This one takes the <code>username</code> as an argument and tries to find a user with it. </p>



<p>If we succeed and the password matches, then we produce a new JWT access token with the appropriate <strong>audience, issuer, </strong>and <strong>username</strong>, expiring in <strong>60 minutes</strong> and signed with <strong>the HMAC-SHA256</strong> algorithm (with our secret key).</p>



<p>Lastly, we add the <code>customValidator</code> function, which we will later use to add an <strong>additional validation:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun customValidator(
  credential: JWTCredential,
): JWTPrincipal? {
  val username: String? = extractUsername(credential)
  val foundUser: User? = username?.let(userService::findByUsername)

  return foundUser?.let {
    if (audienceMatches(credential))
      JWTPrincipal(credential.payload)
    else
      null
  }
}

private fun audienceMatches(
  credential: JWTCredential,
): Boolean =
  credential.payload.audience.contains(audience)

private fun extractUsername(credential: JWTCredential): String? =
  credential.payload.getClaim("username").asString()</pre>



<p>As the first thing, we extract the <em>username</em> from the JWT token and we try to find a user by this value. </p>



<p>If we succeed, then we check if the <em>audience </em>from the token matches the audience set in our Ktor project. If that&#8217;s true, then we instantiate the <strong>JWTPrincipal</strong>, which we will use later to obtain the information. </p>



<h2 class="wp-block-heading" id="h-install-authentication-plugin">Install Authentication Plugin</h2>



<p>At this point, we have the JWT service ready, so it&#8217;s time to install the Authentication plugin. </p>



<p>To do so, let&#8217;s add the <code>Security</code> plugin inside the <code>plugins</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(
  jwtService: JwtService
) {
  authentication {
    jwt {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }
  }
}</pre>



<p>But what exactly is going on here? </p>



<p>The above code installs the Authentication plugin (<code>authentication</code>) with JWT Authentication provider (<code>jwt</code>). </p>



<p>Additionally, we must specify the <code>realm</code>, <code>verifier</code>, and the custom <code>validate</code> function that we implemented previously. </p>



<p>And what if we would like to have <strong>multiple providers in our project?</strong></p>



<p>Well, we can do that easily by simply specifying a new one with a name:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(
  jwtService: JwtService
) {
  authentication {
    jwt {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }

    jwt("another-auth") {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }
  }
}</pre>



<p>Later, we will see how to make use of these JWT auth providers, but for now, let&#8217;s navigate to the <code>Application.kt</code> file and make use of our new config:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSecurity
import com.codersee.plugins.configureSerialization
import com.codersee.repository.UserRepository
import com.codersee.routing.configureRouting
import com.codersee.service.JwtService
import com.codersee.service.UserService
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  val userRepository = UserRepository()
  val userService = UserService(userRepository)
  val jwtService = JwtService(this, userService)

  configureSerialization()
  configureSecurity(jwtService)
  configureRouting(jwtService, userService)
}</pre>



<h2 class="wp-block-heading" id="h-expose-login-endpoint">Expose Login Endpoint</h2>



<p>Perfect! At this point we have everything we need to expose a new endpoint- <code>POST /api/auth</code>&#8211; which we will use to get JWT access tokens for our Ktor app based on the passed username and password. </p>



<p>So as the first step, let&#8217;s go to the <code>Routing</code> file, inject the <code>JwtService</code> and prepare a new route:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  jwtService: JwtService,
  userService: UserService
) {
  routing {

    route("/api/auth") {
      authRoute(jwtService)
    }

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>Nextly, let&#8217;s implement the <code>LoginRequest</code> inside the <code>routing.request</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
  val username: String,
  val password: String,
)</pre>



<p>No traps here this time, so let&#8217;s create the <code>AuthRoute.kt</code> file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.routing.request.LoginRequest
import com.codersee.service.JwtService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Route.authRoute(jwtService: JwtService) {

  post {
    val user = call.receive&lt;LoginRequest>()

    val token: String? = jwtService.createJwtToken(user.username)

    token?.let {
      call.respond(hashMapOf("token" to token))
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

}</pre>



<p>This time, we read the request payload to the <code>LoginRequest</code> instance and we try to create a new JWT access token based on the <em>username</em>. </p>



<p>If we succeed (meaning a user with the following username was found and the password matches), then we return a fresh JWT access token. Otherwise, we simply return <code>401 Unauthorized</code> to the API consumer.</p>



<p>At this point, we can test the app, for example by using curl (and if you would like to get a full Postman collection with automated token handling, then please leave a comment for this article).</p>



<p>Let&#8217;s start by creating a new user:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/api/user' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "user", 
    "password": "password"
}'</pre>



<p>As a response, we should get <code>201 Created</code> with the id in the response header. </p>



<p>Following, let&#8217;s try to get a JWT access token:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/api/auth' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "user", 
    "password": "password"
}'

// Response:

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJteS1hdWRpZW5jZSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvIiwidXNlcm5hbWUiOiJ1c2VyIiwiZXhwIjoxNzAwMTU2Mzg4fQ.DG8SsNrQI5-VqrZeZMFL4IRuVSLeo9Zs9dEifti9nvQ"
}</pre>



<p>Excellent! So far so good. </p>



<p>You can even take this token and check it out using the <a href="https://jwt.io/">jwt.io page</a> 🙂</p>



<h2 class="wp-block-heading" id="h-secure-user-endpoint">Secure User Endpoint</h2>



<p>Finally, let&#8217;s secure user API endpoints: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

  authenticate {
    get {
      val users = userService.findAll()

      call.respond(
        message = users.map(User::toResponse)
      )
    }
  }

  authenticate("another-auth") {
    get("/{id}") {
      val id: String = call.parameters["id"]
        ?: return@get call.respond(HttpStatusCode.BadRequest)

      val foundUser = userService.findById(id)
        ?: return@get call.respond(HttpStatusCode.NotFound)

      if (foundUser.username != extractPrincipalUsername(call))
        return@get call.respond(HttpStatusCode.NotFound)

      call.respond(
        message = foundUser.toResponse()
      )
    }
  }
}

private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password
  )

private fun User.toResponse(): UserResponse =
  UserResponse(
    id = this.id,
    username = this.username,
  )

private fun extractPrincipalUsername(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("username")
    ?.asString()
</pre>



<p>As we can see, after our changes, every <code>GET /api/user</code> request will be verified using the provider, which we specified without the name. </p>



<p>When we check logs, we should even see the confirmation: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Matched routes:<br>&#8220;&#8221; -&gt; &#8220;api&#8221; -&gt; &#8220;user&#8221; -&gt; &#8220;(authenticate &#8220;default&#8221;)&#8221; -&gt; &#8220;(method:GET)&#8221;</cite></blockquote>



<p>On the other hand, every request to <code>GET /api/user/{id}</code> will be handled by the <code>another-auth</code> provider: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>&#8220;&#8221; -&gt; &#8220;api&#8221; -&gt; &#8220;user&#8221; -&gt; &#8220;(authenticate another-auth)&#8221; -&gt; &#8220;{id}&#8221; -&gt; &#8220;(method:GET)&#8221;</cite></blockquote>



<p>Lastly, it&#8217;s worth mentioning that we changed one more thing: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">authenticate("another-auth") {
  get("/{id}") {
    val id: String = call.parameters["id"]
      ?: return@get call.respond(HttpStatusCode.BadRequest)

    val foundUser = userService.findById(id)
      ?: return@get call.respond(HttpStatusCode.NotFound)

    if (foundUser.username != extractPrincipalUsername(call))
      return@get call.respond(HttpStatusCode.NotFound)

    call.respond(
      message = foundUser.toResponse()
    )
  }
}


private fun extractPrincipalUsername(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("username")
    ?.asString()</pre>



<p>In this handler, we obtain the <code>JWTPrincipal</code> (created inside the JwtService) in order to read the <code>username</code>. If it matches with the found user, then we simply return the user response. But if not, then we return <code>404 Not Found</code>. </p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on how to implement a <strong>REST API with Ktor and Kotlin</strong> and how to secure it with <strong>JWT access tokens</strong>.</p>



<p>If you would like to find the whole source code, then check out <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-tokens-authentication" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p>Lastly, if you enjoyed this tutorial, then please do not forget to share it with other people on your social media (it will help me a lot) and check out the continuation for this post:</p>



<ul class="wp-block-list">
<li>h<a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">ow to implement a <strong>refresh token flow </strong>with Ktor</a>,</li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Sending Transactional Emails Using Ktor, Kotlin, and MailerSend</title>
		<link>https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/</link>
					<comments>https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Thu, 02 Feb 2023 10:58:21 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Email]]></category>
		<category><![CDATA[Kotlin]]></category>
		<category><![CDATA[MailerSend]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=5504278</guid>

					<description><![CDATA[<p>In this, hands-on tutorial I will show you how to send transactional emails using Ktor, Kotlin, and MailerSend.</p>
<p>The post <a href="https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/">Sending Transactional Emails Using Ktor, Kotlin, and MailerSend</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Hello and welcome to my next, hands-on tutorial 🙂 This time, I would like to show you <strong>how to send transactional emails using Ktor, Kotlin, and MailerSend</strong>.</p>
<p>Sooner or later, every developer will have to implement email-sending functionality in their application and it&#8217;s worth checking out various solutions, to pick the one that will best meet our needs.</p>
<p>And although the term &#8220;transactional emails&#8221; sounds complicated, in this tutorial I will prove how easily we can use them with Ktor and Kotlin.</p>
<h2>Video Tutorial</h2>
<p>If you prefer <strong>video content</strong>, then check out my video:</p>
<div style="text-align: center; width: 80%; margin-left: 10%;">
<p><a href="https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F29xM0SpzEEg%2Fhqdefault.jpg" alt="YouTube Video"></a></p>
</div>
<p>&nbsp;</p>
<p>If you find this content useful,<strong> please leave a subscription </strong> 😉</p>
<h2>Prerequisites</h2>
<p>Before we start, let&#8217;s see what exactly we will need for this tutorial.</p>
<p>First of all, we will need a <a href="https://www.mailersend.com?ref=fmebcn1eb2vc" target="_blank" rel="noopener">MailerSend account</a>. The free version will be fair enough for the purpose of learning and even simple projects.</p>
<blockquote><p>Note: the above link is a referral and if you will purchase any paid plan, I&#8217;ll get a small commission for that. To be clear: this fact affects the merits of this article in no way 🙂</p></blockquote>
<p>Additionally, <strong>you will need to have a domain, which you can verify</strong>. This is necessary, in order to work with any transactional email providers.</p>
<h2>Configure Domain in MailerSend</h2>
<p>I won&#8217;t go into much detail in this paragraph, because DNS settings will be dependent on your domain registrar. Nevertheless, let&#8217;s take look at how to add a new domain to MailerSend.</p>
<p>So, assuming we created our account and verified the email along with their policies, we should see the following:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504282 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_1.png" alt="Screenshot presents MailerSend dashboard." width="1427" height="738" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_1.png 1427w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_1-300x155.png 300w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_1-1024x530.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_1-768x397.png 768w" sizes="auto, (max-width: 1427px) 100vw, 1427px" /></p>
<p>&nbsp;</p>
<p>As the next step, let&#8217;s click the <strong>Start</strong> button next to the <em>&#8220;Add a sending domain&#8221;</em>:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5504283" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_2.png" alt="Screenshot presents a modal, which allows user to enter a domain name, which we would like to register for transactional emails sending." width="1291" height="618" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_2.png 1291w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_2-300x144.png 300w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_2-1024x490.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_2-768x368.png 768w" sizes="auto, (max-width: 1291px) 100vw, 1291px" /></p>
<p>&nbsp;</p>
<p>Right here, let&#8217;s specify the domain name- <em>test.codersee.com</em> in my case- and confirm with the <strong>Add domain</strong> button.</p>
<p>Nextly, we should see the Domain verification modal:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504284 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_3.png" alt="Screenshot presents domain verification modal with SPF, DKIM and RETURN-PATH settings to copy" width="891" height="1095" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_3.png 891w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_3-244x300.png 244w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_3-833x1024.png 833w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_3-768x944.png 768w" sizes="auto, (max-width: 891px) 100vw, 891px" /></p>
<p>&nbsp;</p>
<p>As we can see, in order to verify our domain, we need to navigate to our domain registrar and modify the DNS settings using the values specified in the modal. When it&#8217;s done, let&#8217;s click the <strong>Check/Re-check</strong> now button.</p>
<blockquote><p>Note: DNS propagation can take some time, so don&#8217;t worry if you see the error messages (just like above). In such a case, give it some time and please get back to this page. MailerSend will send you an e-mail when everything is verified, as well.</p></blockquote>
<p>If everything succeeded, we should see the following page:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504285 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_4.png" alt="" width="1410" height="706" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_4.png 1410w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_4-300x150.png 300w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_4-1024x513.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_4-768x385.png 768w" sizes="auto, (max-width: 1410px) 100vw, 1410px" /></p>
<p>&nbsp;</p>
<p>One <strong>important note</strong> before we head to the next part. At this point, we <strong>will be able to send emails only to addresses and domains we previously verified</strong>&#8211; the e-mail address we use for registration and all addresses in a verified domain in our case.</p>
<p>Nevertheless, <strong>if you would like to send emails outside the domain, you will need to get approval</strong>.</p>
<h2>Generate API Key</h2>
<p>In order to send transactional emails with MailerSend in our Ktor app, we will need to generate a new <strong>API key</strong>.</p>
<p>To do so, let&#8217;s navigate to our domains (Email -&gt; Domains in the left sidebar) and click the manage button:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5504288 size-full" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_5.png" alt="Screenshot presents the test.codersee.com domain details page." width="1397" height="766" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_5.png 1397w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_5-300x164.png 300w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_5-1024x561.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_5-768x421.png 768w" sizes="auto, (max-width: 1397px) 100vw, 1397px" /></p>
<p>&nbsp;</p>
<p>On this page, we can manage API tokens and configure plenty of other things, like webhooks or tracking.</p>
<p>Anyway, let&#8217;s click the Generate new token button:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5504289 size-full" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_6.png" alt="Screenshot presents a MailerSend modal, where we can set a name for the API token and permission level for it." width="618" height="581" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_6.png 618w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_6-300x282.png 300w" sizes="auto, (max-width: 618px) 100vw, 618px" /></p>
<p>&nbsp;</p>
<p>As we can see, right here we can specify the name and access granted to the new token. And although for simplicity we will stick with &#8220;Full access&#8221;, in real-life scenarios we should always follow the principle of least privilege.</p>
<p>Finally, let&#8217;s click the Create token:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5504290 size-full" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_7.png" alt="Screenshot shows a modal with a censored API token value " width="637" height="537" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_7.png 637w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_7-300x253.png 300w" sizes="auto, (max-width: 637px) 100vw, 637px" /></p>
<p>&nbsp;</p>
<p>As can be seen, the API token was created successfully and it&#8217;s time to save the value.</p>
<blockquote><p>Note: please persist this value securely and never share it with anyone!</p></blockquote>
<h2>Generate Ktor Project</h2>
<p>With all of that being done in MailerSend, we can finally switch to the Ktor &amp; Kotlin part of our tutorial about transactional emails.</p>
<p>As always, let&#8217;s navigate to <a href="https://start.ktor.io/" target="_blank" rel="noopener">https://start.ktor.io/</a> and specify desired project settings:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504291 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/01/mailersend_8.png" alt="The image shows Ktor Project Generator page, which we use to generate app skeleton, which then will be used to implement transactional emails with MailerSend." width="702" height="852" srcset="https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_8.png 702w, https://blog.codersee.com/wp-content/uploads/2023/01/mailersend_8-247x300.png 247w" sizes="auto, (max-width: 702px) 100vw, 702px" /></p>
<p>Following, let&#8217;s click the Add plugins (we don&#8217;t need anything else), generate the project, and import it to our IDE.</p>
<p>Nextly, let&#8217;s add the <strong>Ktor client</strong> and <strong>Jackson</strong> dependencies inside the build.gradle.kts:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">implementation("io.ktor:ktor-client-core-jvm:$ktor_version")
implementation("io.ktor:ktor-client-okhttp:$ktor_version")

implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-serialization-jackson:$ktor_version")
</pre>
<p>Of course, Jackson is not the only option here, and GSON, or kotlinx.serialization are well supported in Ktor, as well.</p>
<p>&nbsp;</p>
<p><a href="https://codersee.com/newsletter/"><img loading="lazy" decoding="async" class="aligncenter wp-image-3002956 size-large" src="http://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png" alt="Image shows two ebooks people can get for free after joining newsletter" width="800" height="419" /></a></p>
<h2>Configure Client</h2>
<p>As the next step, let&#8217;s navigate to the<em> application.conf</em> file and add a custom MailerSend config:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">mailersend {
  token = ${MAILERSEND_TOKEN}
  baseUrl = ${MAILERSEND_BASE_URL}
}
</pre>
<p>This way, we can pass the token and URL values securely through environment variables. And although the token will be unique, for the baseUrl, let&#8217;s set <strong>https://api.mailersend.com/v1/</strong>.</p>
<p>Nextly, let&#8217;s create the <strong>MailerSendClient.kt</strong> file and put the following:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">fun Application.configureClient() {

  val token = environment.config.property("mailersend.token").getString()
  val baseUrl = environment.config.property("mailersend.baseUrl").getString()

  val mailerSendClient = HttpClient {
    install(ContentNegotiation) {
      jackson()
    }
  }

  runBlocking {
    // To be done in the next paragraphs
  }
}
</pre>
<p>As we can see, the above function reads configuration properties and installs the <span style="white-space: pre-wrap;"><em>ContentNegotiation</em> plugin using <em>Jackson</em>. Moreover, we&#8217;ve prepared the <em>runBlocking</em> block, where we will invoke our future functions. </span></p>
<p>Lastly, let&#8217;s navigate to the <em>Application.kt</em> file and make use of our config:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">fun Application.module() {
  configureClient()
}
</pre>
<p>At this point, we can run our application and verify that it is working, as expected. If we forget to set environment variables correctly, we should see something like this:</p>
<blockquote><p>Exception in thread &#8220;main&#8221; com.typesafe.config.ConfigException $ UnresolvedSubstitution: application.conf @ [&#8230;] Could not resolve substitution to a value: ${MAILERSEND_BASE_URL}</p></blockquote>
<h2>Send Simple Email</h2>
<p>With all of that done, we can finally start sending transactional emails. In this paragraph, we will learn how to send a simple email with MailerSend.</p>
<h3>Implement EmailRequest</h3>
<p>As the first step, let&#8217;s create a new data class called <strong>EmailRequest</strong>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@JsonInclude(JsonInclude.Include.NON_NULL)
data class EmailRequest(
    val from: Recipient,
    val to: List&lt;Recipient&gt;,
    val subject: String,
    val text: String,
    val html: String,
    val variables: List&lt;Variable&gt;? = null
) {
    data class Recipient(
        val email: String,
        val name: String
    )

    data class Variable(
        val email: String,
        val substitutions: List&lt;Substitution&gt;
    ) {
        data class Substitution(
            @JsonProperty("var") val variable: String,
            val value: String
        )
    }
}
</pre>
<p>Basically, objects of this class will be serialized into JSONs when querying MailerSend API.</p>
<p>The <strong>@JsonInclude</strong> annotation states that if the value is set to null, then it should not be added to the JSON payload (so we won&#8217;t send <code class="EnlighterJSRAW" data-enlighter-language="raw">"whatever": null</code>) . Additionally, MailerSend requires us to send the &#8220;var&#8221; field inside the substitution. Nevertheless, <strong>var</strong> is a reserved keyword in Kotlin, so we have to make use of the <strong>@JsonProperty</strong>.</p>
<h3>Use Ktor Client to Perform Requests</h3>
<p>Following, let&#8217;s add a new function called <em>sendSingleEmail</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">suspend fun sendSingleEmail(
  mailerSendClient: HttpClient,
  url: String,
  token: String,
  emailRequest: EmailRequest
): HttpResponse =
    mailerSendClient.post(url) {
      headers {
        append(HttpHeaders.Authorization, "Bearer $token")
      }
      contentType(ContentType.Application.Json)
      setBody(emailRequest)
    }
</pre>
<p>As we can see, this one is responsible for <strong>sending POST requests to the passed URL </strong>with the <strong>Authorization header</strong> containing the <strong>Bearer token</strong>.</p>
<p>Moreover, the function takes the EmailRequest as an argument, which makes it reusable.</p>
<h3>Send Transactional Client with Ktor Client</h3>
<p>Lastly, let&#8217;s implement the <em>sendSimpleMessage</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">suspend fun sendSimpleMessage(
    mailerSendClient: HttpClient,
    baseUrl: String,
    token: String
) {
    val subject = "Hello from {\$company}!"
    val html = """
        Hello &lt;b&gt;{${'$'}name}&lt;/b&gt;, nice to meet you!
    """.trimIndent()
    val text = """
        Hello {${'$'}name}, nice to meet you!
    """.trimIndent()

    val emailRequest = EmailRequest(
        from = EmailRequest.Recipient(
            email = "example@test.codersee.com",
            name = "Codersee"
        ),
        to = listOf(
            EmailRequest.Recipient(
                email = "example@test.codersee.com",
                name = "Pjoter"
            )
        ),
        subject = subject,
        html = html,
        text = text,
        variables = listOf(
            EmailRequest.Variable(
                email = "example@test.codersee.com",
                substitutions = listOf(
                    EmailRequest.Variable.Substitution(
                        variable = "company",
                        value = "Codersee"
                    ),
                    EmailRequest.Variable.Substitution(
                        variable = "name",
                        value = "Piotr"
                    )
                )
            )
        )
    )

    val response = sendSingleEmail(
        mailerSendClient = mailerSendClient,
        url = "$baseUrl/email",
        token = token,
        emailRequest = emailRequest
    )

    if (response.status == HttpStatusCode.Accepted)
        println("Email sent successfully.")
    else
        println("Error when sending email.")
}
</pre>
<p>And although the above code snippet consists of a lot of lines, it&#8217;s definitely easier than it looks.</p>
<p>Firstly, we prepare the EmailRequest instance, which after serialization, will become this JSON:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">{
    "from": {
        "email": "example@test.codersee.com",
        "name": "Codersee"
    },
    "to": [
        {
            "email": "example@test.codersee.com",
            "name": "Pjoter"
        }
    ],
    "subject": "Hello from {$company}!",
    "html": "Hello &lt;b&gt;{$name}&lt;/b&gt;, nice to meet you!"
    "variables": [
        {
            "email": "example@test.codersee.com",
            "substitutions": [
                {
                    "var": "company",
                    "value": "Codersee"
                },
                {
                    "var": "name",
                    "value": "Piotr"
                }
            ]
        }
    ]
}
</pre>
<p>Whereas most of the fields a pretty descriptive, let&#8217;s take a look at one, important thing- <strong>variables</strong>.</p>
<p>As we can see, we can inject variables into the email subject, or content by simply specifying them with a dollar sign inside curly braces, for example: <em>{$someVariable}</em>. This way, we can specify dynamic values for each recipient, like name and company in our example.</p>
<p>In the next lines, we simply send the request to the MailerSend API, and if the response status code is <strong>202 Accepted</strong>, then the email was sent successfully:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504295 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/02/mailersend_9.png" alt="Screenshot shows an example email send with MailerSend API using Ktor client." width="446" height="260" srcset="https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_9.png 446w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_9-300x175.png 300w" sizes="auto, (max-width: 446px) 100vw, 446px" /></p>
<p>&nbsp;</p>
<p>Of course, in order to make it work, let&#8217;s invoke our function inside the <em>configureClient</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">runBlocking {
  sendSimpleMessage(mailerSendClient, baseUrl, token)
}
</pre>
<h2>Send Emails With Templates</h2>
<p>At this point, we already know how to send transactional emails with Ktor and MailerSend. Moreover, we&#8217;ve seen how to send HTML content, which is a great way to customize the template.</p>
<p>Nevertheless, if you&#8217;d like to learn how to <strong>make your life a bit easier</strong> and use a <strong>graphic builder for templates</strong>, then you&#8217;re gonna like this paragraph.</p>
<h3>Prepare Template Using MailerSend Builder</h3>
<p>As the first step, let&#8217;s navigate to the templates page: <a href="https://app.mailersend.com/templates" target="_blank" rel="noopener">https://app.mailersend.com/templates</a>.</p>
<p>Nextly, let&#8217;s click <strong>Create template</strong> button and select <strong>Drag &amp; drop</strong> editor option:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504296 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/02/mailersend_10.png" alt="Screenshot presents 3 options we can select when creating a new template: drag &amp; drop editor, rich-text editor and HTML editor." width="980" height="687" srcset="https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_10.png 980w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_10-300x210.png 300w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_10-768x538.png 768w" sizes="auto, (max-width: 980px) 100vw, 980px" /></p>
<p>&nbsp;</p>
<p>This way, we&#8217;re redirected to the <strong>Template gallery</strong>, which contains ~50 predefined templates, which we can adjust to our needs. In this tutorial, we will select the <strong>Reset password</strong>:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-5504297 aligncenter" src="http://blog.codersee.com/wp-content/uploads/2023/02/mailersend_11.png" alt="Image presents MailerSend template gallery with highlited Reset passoword template." width="1250" height="763" srcset="https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_11.png 1250w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_11-300x183.png 300w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_11-1024x625.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_11-768x469.png 768w" sizes="auto, (max-width: 1250px) 100vw, 1250px" /></p>
<p>&nbsp;</p>
<p>After we click <strong>Choose</strong>, we&#8217;re redirected to the builder page, where we can make adjustments:</p>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5504298" src="http://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12.png" alt="Screenshot shows MailerSend template builder and edited template." width="500" height="563" srcset="https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12.png 662w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12-266x300.png 266w" sizes="auto, (max-width: 500px) 100vw, 500px" /></p>
<p>&nbsp;</p>
<p>As we can see, when using the builder, we need to encapsulate variables with <strong>double curly brackets</strong>.</p>
<p>When we finish, let&#8217;s click the <strong>Save &amp; Publish</strong> button and <strong>write down the Template ID</strong> on the next page- we will need it later.</p>
<h3>Adjust EmailRequest Class</h3>
<p>With that being done, let&#8217;s get back to the Ktor project and edit the EmailRequest class:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@JsonInclude(JsonInclude.Include.NON_NULL)
data class EmailRequest(
    val from: Recipient,
    val to: List&lt;Recipient&gt;,
    val subject: String,
    @JsonProperty("template_id") val templateId: String,
    val personalization: List&lt;CustomPersonalization&gt;
) {
    data class Recipient(
        val email: String,
        val name: String
    )

    data class CustomPersonalization(
        val email: String,
        val data: PersonalizationData
    ) {
        data class PersonalizationData(
            val name: String,
            @JsonProperty("my_super_generated_code") val code: String
        )
    }
}
</pre>
<p>This time, instead of specifying the content manually, we simply pass the <strong>template identifier</strong> and a <strong>list of personalizations</strong>.</p>
<p>In our case, the desired JSON looks, as follows:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json">{
  "from": {
    "email": "example@test.codersee.com",
    "name": "Codersee"
  },
  "to": [
    {
      "email": "example@test.codersee.com",
      "name": "Pjoter"
    }
  ],
  "subject": "Please Reset Your Password",
  "templateId": "v69oxl587pzl785k",
  "personalization": [
    {
       "email": "example@test.codersee.com",
       "data": {
         "name": "Pjoter",
         "code": "f7fcc37c-4b7a-4919-aa91-a5ac7edd55d7"
       }
     }
  ]
}

</pre>
<h3>Implement Sending Functionality</h3>
<p>With all of that prepared, let&#8217;s implement the <em>sendMessageUsingTemplate</em>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">suspend fun sendMessageUsingTemplate(
    mailerSendClient: HttpClient,
    baseUrl: String,
    token: String
) {
    val subject = "Please Reset Your Password"

    val emailRequest = EmailRequest(
        from = EmailRequest.Recipient(
            email = "example@test.codersee.com",
            name = "Codersee"
        ),
        to = listOf(
            EmailRequest.Recipient(
                email = "example@test.codersee.com",
                name = "Pjoter"
            )
        ),
        subject = subject,
        templateId = "v69oxl587pzl785k",
        personalization = listOf(
            EmailRequest.CustomPersonalization(
                email = "example@test.codersee.com",
                data = EmailRequest.CustomPersonalization.PersonalizationData(
                    name = "Pjoter",
                    code = UUID.randomUUID().toString()
                )
            )
        )
    )

    val response = sendSingleEmail(
        mailerSendClient = mailerSendClient,
        url = "$baseUrl/email",
        token = token,
        emailRequest = emailRequest
    )

    if (response.status == HttpStatusCode.Accepted)
        println("Email sent successfully.")
    else
        println("Error when sending email.")
</pre>
<p>As we can see, the logic itself remains almost exactly the same and the only thing, which changes, is the payload we send.</p>
<p>Lastly, let&#8217;s put the following in the <em>configureClient </em>and rerun the application:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">runBlocking {
  sendSimpleMessage(mailerSendClient, baseUrl, token)
}</pre>
<p>As a result, we should see the message &#8220;Email sent successfully.&#8221; and the following email in our inbox:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5504301" src="http://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12-1.png" alt="Image shows an example email from inbox." width="550" height="556" srcset="https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12-1.png 786w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12-1-297x300.png 297w, https://blog.codersee.com/wp-content/uploads/2023/02/mailersend_12-1-768x777.png 768w" sizes="auto, (max-width: 550px) 100vw, 550px" /></p>
<h2>Handling ErrorsEmails With Templates</h2>
<p>I simply couldn&#8217;t finish this article about sending transactional emails with Ktor and MailerSend without showing <strong>how to handle errors</strong>.</p>
<p>And although we try our best to avoid them, sooner or later they will pop up. Thankfully, MailerSend API informs us in a pretty neat way about any issues. For example:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json">{
    "message": "The template_id must be an integer. (and 2 more errors)",
    "errors": {
        "template_id": [
            "The template_id must be an integer."
        ],
        "to.0.email": [
            "The to.0.email field is required."
        ],
        "variables.0.email": [
            "The variables.0.email field does not exist in to.*.email."
        ]
    }
}
</pre>
<h3>Create ErrorResponse</h3>
<p>As the first step, let&#8217;s implement the ErrorResponse class:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">data class ErrorResponse(
  val message: String,
  val errors: Map&lt;String, List&lt;String&gt;&gt;
)
</pre>
<p>We will use this simple data class to deserialize JSON response into the object.</p>
<h3>Implement handleError</h3>
<p>Nextly, let&#8217;s add the <em>handleError</em> function:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">suspend fun handleError(response: HttpResponse) {
    val statusCode = response.status.value
    val errorBody =  response.body&lt;ErrorResponse&gt;()
    println("Email sending failed with status code $statusCode and message '${errorBody.message}'. Errors:")

    errorBody.errors.forEach { (fieldName, fieldErrors) -&gt;
        println("  * field name: [$fieldName]. Messages:")
        fieldErrors.forEach { error -&gt; println("    - $error") }
    }
}

</pre>
<p>As we can see, this function takes the HttpResponse from MailerSend API and parses the JSON into the ErrorResponse instance. Thanks to that we can iterate and display all the errors.</p>
<p>Of course, as the last step, we need to modify our conditional statement, to make use of this snippet:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">if (response.status == HttpStatusCode.Accepted)
  println("Email sent successfully.")
else 
  println("Error when sending email.")
</pre>
<h2>Sending Emails With Ktor And MailerSend Summary</h2>
<p>And that&#8217;s all for this article about <strong>how to send transactional emails using Ktor, Kotlin, and MailerSend</strong>.</p>
<p>As always, you can find the source code in this <a href="https://github.com/codersee-blog/kotlin-ktor-mailersend-transactional-emails" target="_blank" rel="noopener">GitHub repository.</a></p>
<p>Happy to hear your <strong>thoughts, feedback, or ideas in the comments</strong> 🙂</p>
<p>The post <a href="https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/">Sending Transactional Emails Using Ktor, Kotlin, and MailerSend</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/sending-transactional-emails-ktor-kotlin-mailersend/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-04-29 22:01:47 by W3 Total Cache
-->