<?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>KMongo Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/kmongo/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>KMongo Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>A Guide To Ktor With MongoDB</title>
		<link>https://blog.codersee.com/a-guide-to-ktor-with-mongodb/</link>
					<comments>https://blog.codersee.com/a-guide-to-ktor-with-mongodb/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 15 Mar 2022 06:15:29 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[KMongo]]></category>
		<category><![CDATA[MongoDB]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=2002634</guid>

					<description><![CDATA[<p>In this step by step guide, I would like to show you how to implement a REST API using Ktor, MongoDB and KMongo.</p>
<p>The post <a href="https://blog.codersee.com/a-guide-to-ktor-with-mongodb/">A Guide To Ktor With MongoDB</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 guide, I would like to show you how to implement a REST API using Ktor, MongoDB,<strong> and KMongo</strong>.</p>



<p>If you haven&#8217;t heard about <strong>KMongo</strong> yet- it is a lightweight Kotlin toolkit for Mongo. Internally, it uses the core MongoDB Java Driver and exposes its features via Kotlin extensions. With this combination, we are sure that we&#8217;re using the recommended Java Driver, and additionally, we take advantage of the Kotlin less-verbose syntax.</p>



<p>In the end, I would like to add one more note. In this article, I will show you how to create MongoDB as a Docker container. If you have MongoDB already installed on your local machine, then you can just skip this part. Otherwise, please make sure that you have configured Docker properly in your local environment.</p>



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



<p>With that being said, let&#8217;s head to the <a href="https://start.ktor.io" target="_blank" rel="noopener">Ktor Project Generator page</a> and make a few adjustments.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Alternatively, you can generate the project within the app if you use IntelliJ IDEA Ultimate Edition</p>
</blockquote>



<h3 class="wp-block-heading" id="h-2-1-adjust-ktor-settings">2.1. Adjust Ktor Settings</h3>



<p>As the first step, let&#8217;s make sure that we set correctly the required properties:</p>



<figure class="wp-block-image aligncenter"><img fetchpriority="high" decoding="async" width="255" height="300" src="http://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-1-project-settings-255x300.png" alt="A screenshot from Ktor Project Generator page showing correctly adjustem project settings" class="wp-image-2002635" srcset="https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-1-project-settings-255x300.png 255w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-1-project-settings.png 722w" sizes="(max-width: 255px) 100vw, 255px" /></figure>



<p>Basically, the Project Name, Website, and Artifact are irrelevant and you can adjust it as you want. Nevertheless, to make sure that everything will be working as intended, please make sure to select the same Ktor version on this page.</p>



<h3 class="wp-block-heading" id="h-2-2-add-ktor-plugins">2.2. Add Ktor Plugins</h3>



<p>Nextly, let&#8217;s click the Plugins button, and add <strong>kotlinx.serialization</strong> to our project:</p>



<figure class="wp-block-image aligncenter"><img decoding="async" width="604" height="834" src="http://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-2-add-plugins.png" alt="A screenshot from showing how to add kotlinx serialization in the next step" class="wp-image-2002639" srcset="https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-2-add-plugins.png 604w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-2-add-plugins-217x300.png 217w" sizes="(max-width: 604px) 100vw, 604px" /></figure>



<p>As we can see, two more plugins have been automatically: <strong>ContentNegotiation</strong> and <strong>Routing</strong>:</p>



<figure class="wp-block-image aligncenter"><img decoding="async" width="600" height="757" src="http://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-3-additional-plugins.png" alt="A screenshot from showing two plugins added automatically: Routing and ContentNegotiation added automatically" class="wp-image-2002641" srcset="https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-3-additional-plugins.png 600w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-3-additional-plugins-238x300.png 238w" sizes="(max-width: 600px) 100vw, 600px" /></figure>



<p>Unfortunately, we don&#8217;t have the possibility to add any dependencies required to work with MongoDB and Ktor, so for now, let&#8217;s click <em>Generate Project</em> button and import it to our IDE.</p>


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



<h2 class="wp-block-heading" id="h-3-add-kmongo-dependency">3. Add KMongo Dependency</h2>



<p>After we import the project, let&#8217;s head to the <strong>gradle.properties</strong> file and specify the version of KMongo we&#8217;d like to use:</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="">kmongo_version=4.5.0</pre>



<p>Following, let&#8217;s open the<strong> build.gradle.kts</strong> file and put the following lines:</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="">val kmongo_version: String by project

// Inside the dependencies
implementation("org.litote.kmongo:kmongo:$kmongo_version")</pre>



<h2 class="wp-block-heading" id="h-4-setup-mongodb-container">4. Setup MongoDB Container</h2>



<p>With all of the above being done, let&#8217;s prepare a MongoDB instance with Docker. Let&#8217;s open up the terminal and specify the following 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 run --name my_mongodb_cotainer -d -p 27017:27017 mongo:5.0.6</pre>



<p>Basically, this with this one-liner we perform a few actions:</p>



<ul class="wp-block-list">
<li>with &#8211;name [name] we assign our custom name to the container. Otherwise, a random one will be generated</li>



<li>-d, which is a shortcut for &#8211;detach, runs the container in the background and prints the container ID</li>



<li>with -p host_port:container_port, we simply publish the 27017 port of the local machine to the same port of Mongo. In simple words, it is necessary to connect using <strong>localhost:27017</strong> address</li>



<li>in the end, we specify the image we would like to use- 5.0.6 in our case</li>
</ul>



<p>To validate if everything is running as expected, let&#8217;s run the <em>docker ps</em> command and check the output:</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 run --name my_mongodb_cotainer -d -p 27017:27017 mongo:5.0.6
CONTAINER ID  IMAGE        COMMAND                 CREATED         STATUS         PORTS                     NAMES
3b4369dd37c6  mongo:5.0.6  "docker-entrypoint.s…"  11 minutes ago  Up 11 minutes  0.0.0.0:27017->27017/tcp  my_mongodb_cotainer</pre>



<h2 class="wp-block-heading" id="h-5-ktor-configuration">5. Ktor Configuration</h2>



<p>If we made sure that the MongoDB instance is running, we can get back to our Ktor project and take a look at the configuration.</p>



<p>At this point, our project should consist of three files generated automatically: Application.kt, along with Routing.kt and Serialization.kt inside the <em>plugins</em> package. Please make sure that they are 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="">// Application.kt file:
fun main() {
  embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
    configureRouting()
    configureSerialization()
  }.start(wait = true)
}

// Routing.kt file:
fun Application.configureRouting() {
  routing {
  }
}

// Serialization.kt file:
fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json()
  }
}

</pre>



<p>To put it simply, these three files are responsible for setting up our server to respond on <em>localhost:8080</em> and use <em>kotlinx.serialization</em>. Additionally, the <em>Routing.kt</em> is a skeleton, which we will use later to expose our endpoints.</p>



<h2 class="wp-block-heading" id="h-6-prepare-object-mapping">6. Prepare Object Mapping</h2>



<p>As the next step, let&#8217;s create a <strong>Person</strong> 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="">data class Person(
  @BsonId
  val id: Id&lt;Person>? = null,
  val name: String,
  val age: Int
)
</pre>



<p>As you can see, this simple class except for standard fields consists of the nullable id field annotated with @BsonId. Moreover, we need to use a parametrized Id&lt;T> interface in order to let KMongo know that the Person class is the owner of the given ID. By making this field explicitly null, the id will be generated automatically, so that we won&#8217;t have to pass it explicitly on creation.</p>



<h2 class="wp-block-heading" id="h-7-add-persondto">7. Add <strong>PersonDto</strong></h2>



<p>Following, let&#8217;s add a <strong>PersonDto</strong> to our 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="">@Serializable
data class PersonDto(
  val id: String? = null,
  val name: String,
  val age: Int
)
</pre>



<p>Although it&#8217;s not necessary, this way we will separate objects used to communicate between client and our Ktor app from the ones used to communicate with MongoDB.</p>



<p>Additionally, let&#8217;s create a <strong>PersonExtension.kt</strong> file with two extension 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 Person.toDto(): PersonDto =
  PersonDto(
    id = this.id.toString(),
    name = this.name,
    age = this.age
  )

fun PersonDto.toPerson(): Person =
  Person(
    name = this.name,
    age = this.age
  )
</pre>



<p>These helper functions will serve us as mappers later in the code.</p>



<h2 class="wp-block-heading" id="h-8-implement-error-response">8. Implement Error Response</h2>



<p>As the last step of preparation, let&#8217;s create the ErrorResponse 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="">@Serializable
data class ErrorResponse(val message: String) {
  companion object {
    val NOT_FOUND_RESPONSE = ErrorResponse("Person was not found")
    val BAD_REQUEST_RESPONSE = ErrorResponse("Invalid request")
  }
}
</pre>



<p>Again, this is not necessary but will be helpful when informing clients about errors.</p>



<h2 class="wp-block-heading" id="h-9-establish-mongodb-connection">9. Establish MongoDB Connection</h2>



<p>With all the above being done, let&#8217;s create a <em>PersonService</em>:</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 PersonService {

  private val client = KMongo.createClient()
  private val database = client.getDatabase("person")
  private val personCollection = database.getCollection&lt;Person>()

}
</pre>



<p>Let&#8217;s have a look at each of the above lines:</p>



<ul class="wp-block-list">
<li><em>line 3- </em>creates a Mongo instance using localhost and the default port (27017). If we would like to change connection details, then we can pass it as String, or ConnectionString to its overloaded versions</li>



<li><em>line 4-</em> obtains a Databse instance by the given name. If it does not exist, it will be created during the first insert</li>



<li><em>line 5-</em> gets a Collection (a Table in Mongo terminology). We will use this object to perform all operations</li>
</ul>



<h2 class="wp-block-heading" id="h-10-insert-document-to-mongodb">10. Insert Document To MongoDB</h2>



<p>Following, let&#8217;s get to the heart of this tutorial- Ktor and MongoDB integration.</p>



<h3 class="wp-block-heading" id="h-10-1-add-create-function-to-service">10.1. Add Create Function To Service</h3>



<p>Let&#8217;s start by adding the <em>create()</em> function inside the <strong>PersonService</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 create(person: Person): Id&lt;Person>? {
  personCollection.insertOne(person)
  return person.id
}
</pre>



<p>As we can see, we use an already obtained <strong>MongoCollection</strong> instance to insert a provided Person object. If it is persisted successfully, then the ID will be updated with the value from the database, so that we can return it.</p>



<h3 class="wp-block-heading" id="h-10-2-expose-post-endpoint">10.2. Expose POST Endpoint</h3>



<p>As the next step, let&#8217;s head to the <strong>Routing.kt</strong> and insert the following lines:</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 service = PersonService()

routing {
  route("/person") {
    post {
      val request = call.receive&lt;PersonDto>()
      val person = request.toPerson()

      service.create(person)
        ?.let { userId ->
          call.response.headers.append("My-User-Id-Header", userId.toString())
          call.respond(HttpStatusCode.Created)
        } ?: call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
    }
  }
}
</pre>



<p>Basically, the above logic is pretty straightforward. In the beginning, we obtain the JSON content of the request, which is deserialized to PersonDto instance. Then, we use our extension function to convert it to the Person class. If the document is persisted successfully, we simply return its id to the client setting the <strong>My-User-Id-Header</strong> and <strong>201 Created</strong> status code. On the other hand, if the id is null, we then return an error response along with <strong>400 Bad Request</strong> status.</p>



<h3 class="wp-block-heading" id="h-10-3-test-post-endpoint">10.3. Test POST Endpoint</h3>



<p>Nextly, let&#8217;s run our application and perform a <strong>POST</strong> request:</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="">POST http://localhost:8080/person

Payload:
{
  "name": "Piotr",
  "age": 55
}

Response Status: 
201 Created

Response Headers:
My-User-Id-Header: 6229a38236d4dc3abf0f3174 
</pre>



<p>So far, we don&#8217;t have endpoints to fetch the data, so let&#8217;s use <strong>MongoDB Compass</strong> to check.</p>



<h3 class="wp-block-heading" id="h-10-4-validate-with-mongodb-compass">10.4. Validate With MongoDB Compass</h3>



<p>Let&#8217;s open up the application and simply click Connect:</p>



<figure class="wp-block-image aligncenter"><img loading="lazy" decoding="async" width="632" height="335" src="http://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-4-compass-create-connection.png" alt="A screenshot from MongoDB Compass showing connection details form" class="wp-image-2002644" srcset="https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-4-compass-create-connection.png 632w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-4-compass-create-connection-300x159.png 300w" sizes="auto, (max-width: 632px) 100vw, 632px" /></figure>



<p>Following, select the person database and collection in the left panel:</p>



<figure class="wp-block-image aligncenter"><img loading="lazy" decoding="async" width="900" height="452" src="http://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-5-compass-person-collection.png" alt="A screenshot from MongoDB Compass showing inserted document to the person collection" class="wp-image-2002645" srcset="https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-5-compass-person-collection.png 900w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-5-compass-person-collection-300x151.png 300w, https://blog.codersee.com/wp-content/uploads/2022/03/ktor-mongodb-kmongo-5-compass-person-collection-768x386.png 768w" sizes="auto, (max-width: 900px) 100vw, 900px" /></figure>



<p>As we can see, the document was inserted correctly, which means that our Ktor setup works, as expected.</p>



<h2 class="wp-block-heading" id="h-11-obtain-documents-from-mongodb">11. Obtain Documents From MongoDB</h2>



<p>Nextly, let&#8217;s check how can we obtain documents from MongoDB using KMongo.</p>



<h3 class="wp-block-heading" id="h-11-1-add-findall-function">11.1. Add findAll() function</h3>



<p>Let&#8217;s get back to the <strong>PersonService</strong> and implement the <em>findAll()</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 findAll(): List&lt;Person> =
  personCollection.find()
    .toList()</pre>



<p>It&#8217;s a really simple function and later I will show you how to enhance it with additional filters.</p>



<h3 class="wp-block-heading" id="h-11-2-expose-get-endpoint">11.2. Expose GET Endpoint</h3>



<p>For now, let&#8217;s expose a simple GET 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="">get {
  val peopleList =
      service.findAll()
        .map(Person::toDto)

  call.respond(peopleList)
}</pre>



<p>The above code simply uses our newly created function and maps each <em>Person</em> object using its extension method <em>toDto()</em>.</p>



<h3 class="wp-block-heading" id="h-11-3-test-get-handler">11.3. Test GET Handler</h3>



<p>Finally, let&#8217;s perform a GET request to see if everything is OK:</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="">GET http://localhost:8080/person 

Response Status: 200 OK

Response Payload: 
[
  {
    "id": "6229a38236d4dc3abf0f3174",
    "name": "Piotr",
    "age": 55
  }
]</pre>



<p>As we can see, the previously inserted MongoDB document is returned, so we can assume this part is done.</p>



<h2 class="wp-block-heading" id="h-12-get-document-by-id">12. Get Document By ID</h2>



<p>As the next step, let&#8217;s add the handler responsible for fetching people by their ID.</p>



<h3 class="wp-block-heading" id="h-12-1-implement-findbyid-function">12.1. Implement findById() function</h3>



<p>Let&#8217;s add the <em>findById()</em> within our service:</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 findById(id: String): Person? {
  val bsonId: Id&lt;Person> = ObjectId(id).toId()
  return personCollection
    .findOne(Person::id eq bsonId)
}</pre>



<p>One of the key features of KMongo is a <strong>type-safe query framework</strong>. As we can see above, to make sure that the appropriate argument type is passed to our database, we use a Kotlin property reference. This way, the code won&#8217;t compile if the input type won&#8217;t match the required one. In the next chapter, we will take a deeper look at this topic.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Alternatively, we can use .findOneById() and pass an ObjectId instance to it. Nevertheless, for the learning purpose I&#8217;ve deided to show you this approach, as well.</p>
</blockquote>



<h3 class="wp-block-heading" id="h-12-2-create-get-by-id-handler">12.2. Create GET By ID Handler</h3>



<p>For now, let&#8217;s insert a new handler to our codebase:</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 = call.parameters["id"].toString()

  service.findById(id)
    ?.let { foundPerson -> call.respond(foundPerson.toDto()) }
    ?: call.respond(HttpStatusCode.NotFound, ErrorResponse.NOT_FOUND_RESPONSE)
}</pre>



<p>As we can see- firstly, we obtain the identifier from the request path. If the person with the given ID exists in MongoDB, then <strong>we return PersonDto to our Ktor app client</strong>. In other cases, we simply return <strong>404 Not Found</strong> response.</p>



<h3 class="wp-block-heading" id="h-12-3-validate-get-by-id-endpoint">12.3. Validate GET By ID Endpoint</h3>



<p>Again, let&#8217;s make sure that the above theory meets practice:</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="">GET (existing ID) http://localhost:8080/person/6229a38236d4dc3abf0f3174

Response Status: 200 OK

Response Payload: 
{
  "id": "6229a38236d4dc3abf0f3174",
  "name": "Piotr",
  "age": 55
}

GET (non-existing ID) http://localhost:8080/person/6229a38236d4dc3abf0f3175

Response Status: 404 Not Found

Response Payload: 
{
  "message": "Person was not found"
}</pre>



<p>And again- that is the result we were expecting.</p>



<h2 class="wp-block-heading" id="h-13-search-with-additional-filters">13. Search With Additional Filters</h2>



<p>As the next step, let&#8217;s have a look at the KMongo filtering capabilities.</p>



<h3 class="wp-block-heading" id="h-13-1-create-type-safe-findbyname-method">13.1. Create Type-Safe findByName() method</h3>



<p>To do so, let&#8217;s add a <em>findByName()</em> function 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="">fun findByName(name: String): List&lt;Person> {
  val caseSensitiveTypeSafeFilter = Person::name regex name
  return personCollection.find(caseSensitiveTypeSafeFilter)
    .toList()
}</pre>



<p>To find any document containing a given string in Mongo, we can use leverage <strong>$regex</strong>. Of course, there are other possibilities, like a <em>$text</em> operator, but it does not have its type-safe version in KMongo.</p>



<p>Also, it&#8217;s worth mentioning that the above filter is <strong>case-sensitive.</strong></p>



<h3 class="wp-block-heading" id="h-13-2-add-case-insensitive-regex-filter">13.2. Add Case-Insensitive Regex Filter</h3>



<p>However, if we would like to search for the documents containing the given text regardless of the case, then we can use the overloaded regex 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 caseInsensitiveTypeSafeFilter = personCollection.find(
  (Person::name).regex(name, "i")
)</pre>



<p>As we can see, the above <em>regex</em> extension function allows us to specify additional options. The <strong>&#8220;i&#8221;</strong> flag simply instructs MongoDB to <strong>match both upper and lower cases</strong>.</p>



<p>And again- this approach allows us to make sure that the appropriate argument type is passed each time.</p>



<h3 class="wp-block-heading" id="h-13-3-pass-filter-as-a-string">13.3. Pass Filter As A String</h3>



<p>Although it is more error-prone, the <em>find()</em> function allows us also to specify filters as String values:</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 nonTypeSafeFilter = personCollection.find(
  "{name:{'\$regex' : '$name', '\$options' : 'i'}}"
)</pre>



<p>In some cases, we don&#8217;t have any other possibility and in such a case we have to make sure to handle all the possible exceptions properly.</p>



<h3 class="wp-block-heading" id="h-13-4-and-operator">13.4. AND Operator</h3>



<p>Another useful feature is filter chaining. Let&#8217;s learn how to chain filters with the AND operator:</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 withAndOperator = personCollection.find(
  and(Person::name regex name, Person::age gt 40)
)</pre>



<p>As we can see, the snippet from our Ktor app will look for all people persisted in MongoDB, which <strong>match the given name (case-sensitvie)</strong> and are <strong>older, than 40 years</strong>.</p>



<p>On the other hand, we can refactor the above code 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="">val implicitAndOperator = personCollection.find(
  Person::name regex name, Person::age gt 40
)</pre>



<p>When we pass multiple filters to the <em>find()</em> function, they are<strong> implicitly combined with AND operator</strong>.</p>



<h3 class="wp-block-heading" id="h-13-5-or-operator">13.5. OR Operator</h3>



<p>Similarly, we can add the <strong>OR</strong> operator:</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 withOrOperator = personCollection.find(
  or(Person::name regex name, Person::age gt 40)
)</pre>



<h3 class="wp-block-heading" id="h-13-6-add-ktor-search-handler">13.6. Add Ktor Search Handler</h3>



<p>To summarize the searching topic, let&#8217;s expose a new GET 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="">get("/search") {
  val name = call.request.queryParameters["name"].toString()

  val foundPeople = service.findByName(name).map(Person::toDto)

  call.respond(foundPeople)
}</pre>



<p>This time, we use query parameters to obtain a name to look for. To put it simply, when searching for users with name John, the Ktor app client has to perform a <strong>GET localhost:8080/person/search?name=John</strong> request.</p>



<p>Given the fact I&#8217;ve presented various filters, I highly encourage you to test this endpoint on your own- by adding more data and validating each filter.</p>



<h2 class="wp-block-heading" id="h-14-update-document">14. Update Document</h2>



<p>As the second to last, let&#8217;s see how can we update the person by ID in our project.</p>



<h3 class="wp-block-heading" id="h-14-1-implement-updatebyid">14.1. Implement updateById()</h3>



<p>Firstly, let&#8217;s add this code to our service:</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 updateById(id: String, request: Person): Boolean =
  findById(id)
    ?.let { person ->
      val updateResult = personCollection.replaceOne(person.copy(name = request.name, age = request.age))
      updateResult.modifiedCount == 1L
    } ?: false</pre>



<p>As we can see, firstly we make use of our existing findById function. As a result, we get either found person object, or null. Following we make use of <strong>replaceOne()</strong> function and as an argument we pass the same person instance with updated <em>name</em> and <em>age</em> fields.</p>



<p>As this function is supposed to update document by its unique identifier, it returns true <strong>only when exactly 1 item was modified</strong>.</p>



<h3 class="wp-block-heading" id="h-14-2-create-put-endpoint">14.2. Create PUT Endpoint</h3>



<p>As the next step, let&#8217;s expose a new 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="">put("/{id}") {
  val id = call.parameters["id"].toString()
  val personRequest = call.receive&lt;PersonDto>()
  val person = personRequest.toPerson()

  val updatedSuccessfully = service.updateById(id, person)

  if (updatedSuccessfully) {
    call.respond(HttpStatusCode.NoContent)
  } else {
    call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
  }
}</pre>



<p>This one combines together a few concepts we&#8217;ve learned already and as a result either <strong>204 No Content</strong>, or <strong>400 Bad Request</strong> will be returned.</p>



<h3 class="wp-block-heading" id="h-14-3-test-put-endpoint">14.3. Test PUT Endpoint</h3>



<p>And again, let&#8217;s see whether our endpoint is working correctly:</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="">PUT (existing ID) http://localhost:8080/person/6229a38236d4dc3abf0f3174 

Request Payload: 
{ 
  "name": "Piotr- updated", 
  "age": 20
}

Response Status: 204 No Content

PUT (non-existing ID) http://localhost:8080/person/6229a38236d4dc3abf0f3173 

Request Payload: 
{ 
"name": "Piotr- updated", 
"age": 20
}

Response Status: 400 Bad Request

Response Payload:
{
  "message": "Invalid request"
}</pre>



<p>This time, to validate if the data were persisted correctly, we can either use existing GET by ID endpoint or check with MongoDB Compass.</p>



<h2 class="wp-block-heading" id="h-15-delete-document">15. Delete Document</h2>



<p>Finally, learn how to delete items from MongoDB in our Ktor project.</p>



<h3 class="wp-block-heading" id="h-15-1-create-deletebyid-function">15.1. Create deleteById() Function</h3>



<p>Let&#8217;s start by adding the deleteById() inside the service 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="">fun deleteById(id: String): Boolean {
  val deleteResult = personCollection.deleteOneById(ObjectId(id))
  return deleteResult.deletedCount == 1L
}</pre>



<p>This time, we simply create a new ObjectId and pass it to <strong>deleteOneById()</strong> function. Similarly, the operation was successful only<strong> if the deleted count equals 1</strong>.</p>



<h3 class="wp-block-heading" id="h-15-2-add-delete-handler">15.2. Add DELETE Handler</h3>



<p>Following, let&#8217;s implement the DELETE endpoint handler:</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"].toString()

  val deletedSuccessfully = service.deleteById(id)

  if (deletedSuccessfully) {
    call.respond(HttpStatusCode.NoContent)
  } else {
    call.respond(HttpStatusCode.NotFound, ErrorResponse.NOT_FOUND_RESPONSE)
  }
}</pre>



<p>This time, we simply invoke deleteById function using the String identifier passed by API client. If the Person has been deleted successfuly, we return <strong>204 No Content</strong> response. If that is not true, then we inform the client with <strong>404 status</strong>.</p>



<h3 class="wp-block-heading" id="h-15-3-validate-delete-endpoint">15.3. Validate DELETE Endpoint</h3>



<p>Finally, let&#8217;s see if everything was set up correctly:</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="">DELETE (existing ID) http://localhost:8080/person/6229a38236d4dc3abf0f3174 

Response Status: 204 No Content

DELETE (we can use the same ID) http://localhost:8080/person/6229a38236d4dc3abf0f3174

Response Status: 404 Not Found
Response Payload:
{
  "message": "Person was not found"
}</pre>



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



<h2 class="wp-block-heading" id="h-16-ktor-with-mongodb-and-kmongo-summary">16. Ktor With MongoDB and KMongo Summary</h2>



<p>And that would be all for this step-by-step guide on creating a CRUD REST API with Ktor, MongoDB,<strong> and KMongo</strong>.</p>



<p>I really hope that my materials will help you to learn new things and will be happy if you would like to share some feedback with me using the <a href="https://codersee.com/contact/"><strong>contact</strong></a> form.</p>



<p>If you would like to get the full source code for this project, please refer to this <a href="https://github.com/codersee-blog/ktor-mongodb-kmongo" target="_blank" rel="noopener"><strong>GitHub repository</strong></a>.</p>
<p>The post <a href="https://blog.codersee.com/a-guide-to-ktor-with-mongodb/">A Guide To Ktor With MongoDB</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/a-guide-to-ktor-with-mongodb/feed/</wfw:commentRss>
			<slash:comments>5</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-18 15:13:10 by W3 Total Cache
-->