<?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>Ktorm Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/ktorm/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:50:21 +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>Ktorm Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>REST API With Ktor, Ktorm and PostgreSQL</title>
		<link>https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/</link>
					<comments>https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Sun, 23 Jan 2022 07:00:52 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Ktorm]]></category>
		<category><![CDATA[PostgreSQL]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=1002001</guid>

					<description><![CDATA[<p>In this step by step tutorial I will show you how to implement a simple REST API with Ktor, Ktorm and PostgreSQL.</p>
<p>The post <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/">REST API With Ktor, Ktorm and PostgreSQL</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-1-introduction">1. Introduction</h2>



<p>In this step-by-step tutorial, I would like to show you how to create a simple <strong>REST API</strong> using <a href="https://ktor.io/" target="_blank" rel="noopener">Ktor</a>, <a href="https://www.ktorm.org/" target="_blank" rel="noopener">Ktorm</a>, and <a href="https://www.postgresql.org/" target="_blank" rel="noopener">PostgreSQL (aka Postgres)</a>.</p>



<p><strong>Ktor</strong> is a free, open-source framework, which allows us to easily build connected applications. When it comes to server-side applications, we can treat it as a lightweight and powerful alternative to Spring Boot. Additionally, it was written in Kotlin programming language by its&#8217; creators- the JetBrains team.</p>



<p><strong>Ktorm</strong>, on the other hand, is a lightweight and efficient <strong>ORM Framework for Kotlin</strong> directly based on pure JDBC. Furthermore, combined with <strong>PostgreSQL</strong> makes an interesting combination for a REST API tech stack.</p>



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



<p>With all of that being said, let&#8217;s create the Ktor project (and in the next chapter, I will show you how to add Ktorm and PostgreSQL dependencies).</p>



<p>If you are the owner of <em>IntelliJ Ultimate Edition</em>, then it ships with Ktor support out of the box. Nevertheless, in this tutorial, we&#8217;ll focus on the alternative: <a href="https://start.ktor.io" target="_blank" rel="noopener">https://start.ktor.io</a>, which is pretty similar to the <em>Spring Initializr</em> page.</p>



<p>As the first step, let&#8217;s specify the project name along with other, necessary data:</p>



<figure class="wp-block-image aligncenter"><img fetchpriority="high" decoding="async" width="730" height="868" src="http://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-1.png" alt="Image shows project generator page used to generate our ktor with ktorm and postgresql project" class="wp-image-1002002" srcset="https://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-1.png 730w, https://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-1-252x300.png 252w" sizes="(max-width: 730px) 100vw, 730px" /></figure>



<p>Please remember about selecting the same Ktor version, so that the code from this tutorial will work for sure. Don&#8217;t worry about a sample code generated, we will take care of it later.</p>



<p>As the next step, let&#8217;s click the<strong> Add plugins</strong> button and add the <strong>kotlinx.serialization</strong>&#8211; we&#8217;ll use it to serialize our objects into JSON format:</p>



<figure class="wp-block-image aligncenter"><img decoding="async" width="646" height="642" src="http://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-2.png" alt="Image shows ktor project generator page" class="wp-image-1002003" srcset="https://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-2.png 646w, https://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-2-300x298.png 300w, https://blog.codersee.com/wp-content/uploads/2022/01/ktor-ktorm-postgresql-2-150x150.png 150w" sizes="(max-width: 646px) 100vw, 646px" /></figure>



<p>As we can see, two other plugins have been added automatically: <strong>ContentNegotiation</strong> and <strong>Routing</strong>. Please do not remove them, we will need them later.</p>


<p>[elementor-template id=&#8221;9007393&#8243;]</p>



<h2 class="wp-block-heading" id="h-2-add-ktorm-and-postgresql-to-ktor">2. Add Ktorm and PostgreSQL To Ktor</h2>



<p>Nextly, let&#8217;s open up our Ktor Postgres project with IntelliJ and go to the <strong>gradle.properties</strong> file.</p>



<p>Unfortunately, the Project Generator does not allow us to add all of the necessary imports, so we will have to do it manually:</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="">postgresql_driver_version=42.3.1
ktorm_version=3.4.1</pre>



<p>As the next step, let&#8217;s add a few lines within the <strong>build.gradle.kts</strong> file and <strong>reload the Gradle project</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="">val ktorm_version: String by project
val postgresql_driver_version: String by project

dependencies {

// other dependencies

implementation("org.ktorm:ktorm-core:$ktorm_version")
implementation("org.ktorm:ktorm-support-postgresql:$ktorm_version")
implementation("org.postgresql:postgresql:$postgresql_driver_version")

}</pre>



<p>As we can see, these dependencies are necessary in order to connect our Ktor application with the Postgres database.</p>



<h2 class="wp-block-heading" id="h-3-clean-up-the-code">3. Clean Up The Code</h2>



<p>After that, let&#8217;s clean up the project a bit and delete the <strong>Routing.kt</strong> along with its invocation within the <strong>Application.kt</strong>.</p>



<p>The second file should now be looking, as follows:</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") {
    configureSerialization()
  }.start(wait = true)
}</pre>



<p>Then, let&#8217;s go to the <strong>Serialization.kt</strong> file and remove unnecessary code, 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="">fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json()
  }
}</pre>



<p>The above code will be necessary for JSON serialization.</p>



<h2 class="wp-block-heading" id="h-4-prepare-postgresql-with-docker-compose">4. Prepare PostgreSQL With Docker Compose</h2>



<p>Basically, if you already have a PostgreSQL instance running on your machine, you can just skip the docker-compose part and just run the below SQL script.</p>



<p>Either way, let&#8217;s create the <strong>sql</strong> folder in the root directory and add the <strong>init-db.sql</strong> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="sql" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">CREATE TABLE book
(
  id   SERIAL NOT NULL,
  name varchar NOT NULL
);

INSERT INTO book(name) VALUES ('Book #1');
INSERT INTO book(name) VALUES ('Book #2');</pre>



<p>We can clearly see, that after the script is run, a new table will be added and populated with test data.</p>



<p>Following, let&#8217;s go to the root of the Ktor project and add the <strong>docker-compose.yaml</strong> file with Postgres definition:</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="">version: "3.9"
services:
  postgres-sandbox:
    image: postgres:14
    ports:
      - '5438:5432'
    volumes:
      - ./sandbox-db:/var/lib/postgresql/data
      - ./sql/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
    environment:
      - POSTGRES_NAME=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres</pre>



<p>The above code is responsible for a few things:</p>



<ul class="wp-block-list">
<li>pulling the Docker image for PostgreSQL</li>



<li>publishing the container&#8217;s <strong>5432</strong> port to the host&#8217;s <strong>5438 </strong>(to put it simply- we will connect from our application using port <strong>5438</strong>)</li>



<li>running initialization script prepared above</li>



<li>and finally, setting credentials through environment variables</li>
</ul>



<p>To start our PostgreSQL instance, let&#8217;s run the below command:</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="">docker-compose up

# To validate:
docker ps
CONTAINER ID IMAGE       COMMAND                CREATED       STATUS       PORTS                  NAMES
2f5716235039 postgres:14 "docker-entrypoint.s…" 6 minutes ago Up 6 minutes 0.0.0.0:5438->5432/tcp ktor-ktorm-postgresql_postgres-sandbox_1

</pre>



<p>As we can see, everything is working fine and now we will be able to connect our Ktor app with Postgres.</p>



<h2 class="wp-block-heading" id="h-5-implement-dtos">5. Implement DTOs</h2>



<p>Following, let&#8217;s prepare request and response classes, which will be used in our REST API. Technically, we don&#8217;t have to do that, but it&#8217;s a good practice to keep them separate.</p>



<p>Let&#8217;s start with the <strong>BookRequest</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="">@Serializable
data class BookRequest(
  val name: String
)</pre>



<p>Similarly, let&#8217;s implement the <strong>BookResponse </strong>and <strong>ErrorResponse</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="">@Serializable
data class BookResponse(
  val id: Long,
  val name: String
)

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



<p>You might have noticed, that both classes are annotated with <strong>@Serializable</strong> (from <strong>kotlinx.serialization </strong>library). According to the documentation:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Applying Serializable to the Kotlin class instructs the serialization plugin to automatically generate implementation of KSerializer for the current class, that can be used to serialize and deserialize the class.</p>
</blockquote>



<p>To put it simply, this annotation is necessary if we want to operate on these two classes in our REST API.</p>



<h2 class="wp-block-heading" id="h-6-add-entity-and-columns-bindings">6. Add Entity And Columns Bindings</h2>



<p>With all of that being done, let&#8217;s add the code responsible for <strong>object-relational mapping</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="">interface Book : Entity&lt;Book> {
  companion object : Entity.Factory&lt;Book>()

  val id: Long?
  var name: String
}

object Books : Table&lt;Book>("book") {
  val id = long("id").primaryKey().bindTo(Book::id)
  val name = varchar("name").bindTo(Book::name)
}</pre>



<p>It may seem a bit counterintuitive in the beginning, so let&#8217;s spend some time understanding it better.</p>



<p>The first one, <strong>Book</strong>, is a representation of the entity class, which will be used to operate on it. You might have already noticed, that it&#8217;s been declared as an interface- <strong>that’s a design requirement of Ktorm</strong>. (Ktorm 2.5 introduced the possibility to declare them as classes, but this approach has some limitations, which you can read more about<a href="https://www.ktorm.org/en/define-entities-as-any-kind-of-classes.html" target="_blank" rel="noopener"> here</a>).</p>



<p>Additionally, you might be curious, what exactly does this line do?</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="">companion object : Entity.Factory&lt;Book>()
</pre>



<p>Basically, interfaces cannot be instantiated, but to help us with that, Ktorm provides an abstract class <strong>Entity.Factory</strong>. It overloads the <strong>invoke</strong> operator so that we will be able to create Book instances and initialize its properties pretty easily:</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 newBook = Book {
  name = bookRequest.name
}
</pre>



<p>Finally, the second part, an object called <strong>Books</strong> is responsible for binding our entity with the database table.</p>



<p>It allows us to specify columns types, and names and wire them with our entity through the <strong>.bindTo()</strong> method. Whatsoever, we will use this object later, when working with our Ktor Postgres database connection.</p>



<h2 class="wp-block-heading" id="h-7-connect-to-postgresql">7. Connect To PostgreSQL</h2>



<p>As the next step, let&#8217;s implement the <strong>BookService</strong> and connect to the 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="">class BookService {

  private val database = Database.connect(
    url = "jdbc:postgresql://localhost:5438/postgres",
    driver = "org.postgresql.Driver",
    user = "postgres",
    password = "postgres"
  )
}</pre>



<p>As we can see, the connect function allows us to specify Ktor Postgres connection details. Please make sure that these values match the actual state in your case. As a result, we receive a new database object, which we will use to perform DB operations.</p>



<h2 class="wp-block-heading" id="h-8-implement-service-methods">8. Implement Service Methods</h2>



<p>With all of that being done, we can finally add logic responsible for books management.</p>



<p>Let&#8217;s go to the <strong>BookService</strong> and add 4 methods:</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 createBook(bookRequest: BookRequest): Boolean {
  val newBook = Book {
    name = bookRequest.name
  }

  val affectedRecordsNumber = 
    database.sequenceOf(Books)
      .add(newBook)

  return affectedRecordsNumber == 1
}

fun findAllBooks(): Set&lt;Book> =
  database.sequenceOf(Books).toSet()

fun findBookById(bookId: Long): Book? =
  database.sequenceOf(Books)
    .find { book -> book.id eq bookId }

fun updateBookById(bookId: Long, bookRequest: BookRequest): Boolean {
  val foundBook = findBookById(bookId)
    foundBook?.name = bookRequest.name

  val affectedRecordsNumber = foundBook?.flushChanges()

  return affectedRecordsNumber == 1
}
fun deleteBookById(bookId: Long): Boolean {
  val foundBook = findBookById(bookId)

  val affectedRecordsNumber = foundBook?.delete()

  return affectedRecordsNumber == 1
}</pre>



<h3 class="wp-block-heading" id="h-8-1-create-book">8.1. Create Book</h3>



<p>Let&#8217;s take a while to analyze 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="">fun createBook(bookRequest: BookRequest): Boolean {
  val newBook = Book {
    name = bookRequest.name
  }

  val affectedRecordsNumber = 
    database.sequenceOf(Books)
      .add(newBook)

  return affectedRecordsNumber == 1
}
</pre>



<p>As we can see, the <strong>createBook() </strong>creates a new Book instance (thanks to the companion object) and inserts it into the table using the <strong>database</strong> object.</p>



<p>The <strong>sequenceOf()</strong>, instantiates a new <strong>EntitySequence</strong> object. It exposes a sequence API, which we can use to perform DB operations, like querying or inserting data.</p>



<p>Finally, the <strong>.add()</strong> method inserts our entity into the database and returns the number of affected records.</p>



<p>The last line of this function simply validates, whether the number of affected records is equal to one (any other number indicates that something went wrong).</p>



<h3 class="wp-block-heading" id="h-8-2-fetching-books">8.2. Fetching Books</h3>



<p>Nextly, let&#8217;s check the querying 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 findAllBooks(): Set&lt;Book> =
  database.sequenceOf(Books).toSet()

fun findBookById(bookId: Long): Book? =
  database.sequenceOf(Books)
    .find { book -> book.id eq bookId }
</pre>



<p>They are pretty much the same, except for the fact, that in the second case the <strong>.find{}</strong> function will be translated to the WHERE clause in the generated SQL.</p>



<h3 class="wp-block-heading" id="h-8-3-update-book">8.3. Update Book</h3>



<p>As the next one, let&#8217;s check out the <strong>updateBookById()</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 updateBookById(bookId: Long, bookRequest: BookRequest): Boolean {
  val foundBook = findBookById(bookId)
  foundBook?.name = bookRequest.name

  val affectedRecordsNumber = foundBook?.flushChanges()

  return affectedRecordsNumber == 1
}</pre>



<p>We can clearly see, that we can operate on the fetched entity, just like on any object in Kotlin. Nevertheless, for the changes to take effect, we must invoke the <strong>.flushChanges()</strong> function, which returns the affected records number.</p>



<h3 class="wp-block-heading" id="h-8-4-delete-book">8.4. Delete Book</h3>



<p>Finally, the <strong>deleteBookById</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 deleteBookById(bookId: Long): Boolean {
  val foundBook = findBookById(bookId)

  val affectedRecordsNumber = foundBook?.delete()

  return affectedRecordsNumber == 1
}</pre>



<p>Although it&#8217;s performing a different action, the mechanism behind it remains the same.</p>



<h2 class="wp-block-heading" id="h-9-implement-book-routes">9. Implement Book Routes</h2>



<p>With all of that being done, we can finally add the necessary routes.</p>



<p>In Ktor, <strong>Routing</strong> is a core plugin responsible for handling incoming requests. If you&#8217;ve ever been working with Spring Boot before, then you may find it similar to the Controller concept.</p>



<h3 class="wp-block-heading" id="h-9-1-edit-application-kt">9.1. Edit Application.kt</h3>



<p>As the first step, let&#8217;s get back to the <strong>Application.kt</strong> and add <strong>configureBookRoutes()</strong> invocation:</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") {
    configureBookRoutes()
    configureSerialization()
  }.start(wait = true)
}</pre>



<h3 class="wp-block-heading" id="h-9-2-create-bookroutes-kt">9.2. Create BookRoutes.kt</h3>



<p>Secondly, let&#8217;s add the <strong>BookRoutes.kt</strong> file with the necessary 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 Application.configureBookRoutes() {
  routing {
    route("/book") {
      val bookService = BookService()
      createBook(bookService)
      getAllBooksRoute(bookService)
      getBookByIdRoute(bookService)
      updateBookByIdRoute(bookService)
      deleteBookByIdRoute(bookService)
    }
  }
}

private fun Book?.toBookResponse(): BookResponse? =
  this?.let { BookResponse(it.id!!, it.name) }</pre>



<p>To put it simply, the <strong>routing</strong> block installs a Routing feature in our application. On the other hand, the <strong>route </strong>builds a route to match the specified path. This way, we can keep relevant functions together and avoid duplicating the <em>/book</em> path for each of them.</p>



<p>When it comes to the <strong>toBookResponse()</strong>, this extension function will help us to convert <strong>Book</strong> entities into <strong>BookResponses</strong>.</p>



<h3 class="wp-block-heading" id="h-9-3-add-create-book-route">9.3. Add Create Book Route</h3>



<p>With this in mind, let&#8217;s implement the rest of the code step by step. As the first one, let&#8217;s add the <strong>createBook()</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 Route.createBook(bookService: BookService) {
  post {
    val request = call.receive&lt;BookRequest>()

    val success = bookService.createBook(bookRequest = request)

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



<p>The above code will be responsible for handling <em>POST /book</em> requests. As a word of explanation, the <strong>call.receive&lt;BookRequest&gt;()</strong> deserializes the content of the request (JSON) into the BookRequest instance. When the content cannot be translated, it throws <strong>ContentTransformationException</strong> (it&#8217;s worth to remember in real-life scenarios).</p>



<p>On the contrary, the <strong><span class="enlighter-text"> call.</span><span class="enlighter-m3">respond</span>()</strong> is responsible for sending responses to the client. We can clearly see that if creation is successful, the <strong>201 Created</strong> is returned. However, if this is not the case, then the <strong>400 Bad Request</strong> with an appropriate message is returned.</p>



<h3 class="wp-block-heading" id="h-9-4-implement-get-all-books-route">9.4. Implement Get All Books Route</h3>



<p>As the next step, let&#8217;s add the <strong>getAllBooksRounte()</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 Route.getAllBooksRoute(bookService: BookService) {
  get {
    val books = bookService.findAllBooks()
      .map(Book::toBookResponse)

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



<p>This one will be triggered on <em>GET /book</em> requests returning the list of books.</p>



<h3 class="wp-block-heading" id="h-9-5-add-get-book-by-id-route">9.5. Add Get Book By Id Route</h3>



<p>With that being done, let&#8217;s add the <strong>getBookByIdRoute()</strong> responsible for handling GET /book/{id} 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="">fun Route.getBookByIdRoute(bookService: BookService) {
  get("/{id}") {
    val id: Long = call.parameters["id"]?.toLongOrNull()
      ?: return@get call.respond(HttpStatusCode.BadRequest, ErrorResponse("Invalid id"))

    bookService.findBookById(id)
      ?.let { foundBook -> foundBook.toBookResponse() }
      ?.let { response -> call.respond(response) }
      ?: return@get call.respond(HttpStatusCode.BadRequest, ErrorResponse("Book with id [$id] not found"))
  }
}</pre>



<p>This time, let&#8217;s focus on the <strong>call.parameters[&#8220;id&#8221;]</strong>. The Parameters property of the call is simply a map of case-insensitive names to a collection of String values. That&#8217;s why we have to use a safe <strong>?.toLongOrNull()</strong> to obtain the value correctly. If the id is invalid, then the <strong>400 Bad Request</strong> is returned.</p>



<p>The rest of the code is pretty straightforward- either the found book is mapped to the <strong>BookResponse</strong> instance and returned to the client, or a <strong>400 Bad Reques</strong>t informing that the book with a given id was not found.</p>



<h3 class="wp-block-heading" id="h-9-6-insert-update-and-delete-routes">9.6. Insert Update and Delete Routes</h3>



<p>Finally, let&#8217;s add the two routes responsible for handling <em>PATCH /book/{id}</em> and <em>DELETE /book/{id}</em> 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="">fun Route.updateBookByIdRoute(bookService: BookService) {
  patch("/{id}") {
    val id: Long = call.parameters["id"]?.toLongOrNull()
      ?: return@patch call.respond(HttpStatusCode.BadRequest, ErrorResponse("Invalid id"))

    val request = call.receive&lt;BookRequest>()
    val success = bookService.updateBookById(id, request)

    if (success)
      call.respond(HttpStatusCode.NoContent)
    else
      call.respond(HttpStatusCode.BadRequest, ErrorResponse("Cannot update book with id [$id]"))
  }
}

fun Route.deleteBookByIdRoute(bookService: BookService) {
  delete("/{id}") {
    val id: Long = call.parameters["id"]?.toLongOrNull()
      ?: return@delete call.respond(HttpStatusCode.BadRequest, ErrorResponse("Invalid id"))

    val success = bookService.deleteBookById(id)

    if (success)
      call.respond(HttpStatusCode.NoContent)
    else
      call.respond(HttpStatusCode.BadRequest, ErrorResponse("Cannot delete book with id [$id]"))
  }
}</pre>



<p>As we can see, these functions use already described concepts, so we won&#8217;t go into much detail.</p>



<h2 class="wp-block-heading" id="h-10-test-with-curl">10. Test With cURL</h2>



<p>Finally, let&#8217;s run the application and test it manually. For simplicity, we will use the <strong>cURL</strong> command. Alternatively, we could do that through Postman, or Insomnia.</p>



<h3 class="wp-block-heading" id="h-10-1-get-book">10.1. GET /book</h3>



<p>Let&#8217;s start by listing all books to validate both the endpoint and SQL init script:</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="">curl localhost:8080/book

# Output
[
  {
    "id":1,
    "name": "Book #1"
  },
  {
    "id":2,
    "name":"Book #2"
  }
]</pre>



<p>As we can see, everything is working correctly.</p>



<h3 class="wp-block-heading" id="h-10-2-post-book">10.2. POST /book</h3>



<p>As the second step, let&#8217;s create a new book:</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="">curl -X POST 'localhost:8080/book' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "name": "Book #3"
  }'

# Then again: 
curl localhost:8080/book

# Output
[
  {
    "id":1,
    "name": "Book #1"
  },
  {
    "id":2,
    "name":"Book #2"
  },
  {
    "id":3,
    "name":"Book #3"
  }
]</pre>



<h3 class="wp-block-heading" id="h-10-3-get-book-id">10.3. GET /book/{id}</h3>



<p>As the third step, let&#8217;s check whether fetching book details works:</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="">curl localhost:8080/book/3

# Output:
{
  "id":3,
  "name":"Book #3"
}

curl localhost:8080/book/1234

# Output:
{
  "message":"Book with id [1234] not found"}
}</pre>



<h3 class="wp-block-heading" id="h-10-4-patch-book-id">10.4. PATCH /book/{id}</h3>



<p>After that, let&#8217;s validate the PATCH endpoint:</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="">curl -X PATCH 'localhost:8080/book/3' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "name": "Book #3- edited"
  }'

# Then, let's check with GET:
curl localhost:8080/book/3

# Output:
{
  "id": 3,
  "name": "Book #3- edited"
}</pre>



<h3 class="wp-block-heading" id="h-10-5-delete-book-id">10.5. DELETE /book/{id}</h3>



<p>Finally, let&#8217;s delete created book:</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="">curl -X DELETE 'localhost:8080/book/3'

# Then, let's check with GET:
curl localhost:8080/book/3

# Output:
{
  "message":"Book with id [3] not found"
}</pre>



<h2 class="wp-block-heading" id="h-11-ktor-with-ktorm-and-postgresql-summary">11. Ktor With Ktorm And PostgreSQL Summary</h2>



<p>And that would be all for this step-by-step guide on <strong>how to create REST API with Ktor, Ktorm, and PostgreSQL</strong>. I really hope you will find this material useful when working with <strong>Ktor</strong>. If you would like to see the source code, please refer to <a href="https://github.com/codersee-blog/ktor-ktorm-postgresql" target="_blank" rel="noopener">this GitHub repository</a>.</p>



<p>If you find this one useful, then you may enjoy my newer article about <a href="https://blog.codersee.com/a-guide-to-ktor-with-mongodb/">Ktor and MongoDB</a>.</p>



<p>As always- if you would like to ask me about anything, please do that in the comments section below, or through the <a href="https://codersee.com/contact/" target="_blank" rel="noopener">contact form</a>.</p>



<h4 class="wp-block-heading" id="h-previous-articles-you-might-be-interested-in">Previous articles, you might be interested in:</h4>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/exception-handling-with-restcontrolleradvice-and-exceptionhandler/" target="_blank" rel="noopener">Exception Handling with @RestControllerAdvice and @ExceptionHandler</a></li>



<li><a href="https://blog.codersee.com/16-intellij-shortcuts-that-will-boost-your-productivity/" target="_blank" rel="noopener">16 IntelliJ Shortcuts That Will Boost Your Productivity</a></li>



<li><a href="https://blog.codersee.com/solid-principles-with-kotlin-examples/" target="_blank" rel="noopener">SOLID Principles with Kotlin Examples</a></li>
</ul>
<p>The post <a href="https://blog.codersee.com/rest-api-ktor-ktorm-postgresql/">REST API With Ktor, Ktorm and PostgreSQL</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/rest-api-ktor-ktorm-postgresql/feed/</wfw:commentRss>
			<slash:comments>0</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-05-11 18:04:38 by W3 Total Cache
-->