MongoDB.local SF, Jan 15: See the speaker lineup & ship your AI vision faster. Use WEB50 to save 50%
Find out more >
Docs Menu
Docs Home
/ /

Create an API By Using Ktor and MongoDB Atlas

Ktor is a Kotlin-based asynchronous web framework designed for building modern web applications and APIs. It offers robust support for asynchronous programming with Kotlin coroutines.

In this tutorial, you will learn how to set up a runnable API using Ktor, the Kotlin driver, and MongoDB Atlas.

1

Before you begin this tutorial, ensure that you have the following:

  • A MongoDB Atlas account with a cluster. To view instructions, see the Get Started guide.

  • IntelliJ IDEA.

2

Create a new Ktor project by using the Ktor Project Generator. Name the project artifact com.mongodb.fitness-tracker using the text entry field. Click the Configure button and specify the following options:

  • Build System: Gradle Kotlin

  • Engine: Tomcat

  • Configuration: HOCON File

Using the Plugins menu on the New Ktor Project page, add the following plugins to your project:

  • Content Negotiation: Negotiates media types between the client and server

  • GSON: Provides serialization and deserialization support

  • Routing: Manages incoming requests

  • Swagger: Generates API documentation

Click the Generate Project button to download a ZIP file containing your new Ktor project.

Unzip the downloaded file and open the build.gradle.kts file in the root directory of the project directory. Add the following plugins to the plugins block:

plugins {
kotlin("jvm") version "1.9.22"
id("io.ktor.plugin") version "2.3.7"
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
}

Add the following dependencies to the dependencies block in the same file:

dependencies {
implementation("io.ktor:ktor-server-core-jvm")
implementation("io.ktor:ktor-server-swagger-jvm")
implementation("io.ktor:ktor-server-content-negotiation-jvm")
implementation("io.ktor:ktor-serialization-gson-jvm")
implementation("io.ktor:ktor-server-tomcat-jvm")
implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.ktor:ktor-server-config-yaml:2.3.8")
// MongoDB
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.6.1")
// Koin dependency injection
implementation("io.insert-koin:koin-ktor:3.5.3")
implementation("io.insert-koin:koin-logger-slf4j:3.5.3")
// Client
implementation("io.ktor:ktor-client-core:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
}
3

Create the following three packages under the src/main/kotlin directory:

  • application

  • domain

  • infrastructure

Inside the domain package, create a new sub-package named entity, then create a file within entity named Fitness.kt. Copy the following code into the file:

package com.mongodb.domain.entity
import com.mongodb.application.response.FitnessResponse
import org.bson.codecs.pojo.annotations.BsonId
import org.bson.types.ObjectId
data class Fitness(
@BsonId
val id: ObjectId,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
){
fun toResponse() = FitnessResponse(
id = id.toString(),
exerciseType = exerciseType,
notes = notes,
details = details
)
}
data class FitnessDetails(
val durationMinutes: Int,
val distance: Double,
val caloriesBurned: Int
)

The toResponse() method in the preceding code throws an error because you haven't created the FitnessResponse class yet. Create request and response packages under the application/request packag and create two files named FitnessRequest.kt and FitnessResponse.kt in the corresponding package.

Copy the following code into the FitnessRequest.kt file:

package com.mongodb.application.response
import com.mongodb.domain.entity.FitnessDetails
data class FitnessResponse(
val id: String,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
)

Copy the following code into the FitnessResponse.kt file:

package com.mongodb.application.response
import com.mongodb.domain.entity.FitnessDetails
data class FitnessResponse(
val id: String,
val exerciseType: String,
val notes: String,
val details: FitnessDetails
)

Create a ports package under the domain package and create a file named FitnessRepository.kt in the ports package. This file represents the interface that will communicate with the database. Copy the following code into the FitnessRepository.kt file:

package com.mongodb.domain.ports
import com.mongodb.domain.entity.Fitness
import org.bson.BsonValue
import org.bson.types.ObjectId
interface FitnessRepository {
suspend fun insertOne(fitness: Fitness): BsonValue?
suspend fun deleteById(objectId: ObjectId): Long
suspend fun findById(objectId: ObjectId): Fitness?
suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long
}

Create a repository package under the infrastructure package and create a file named FitnessRepositoryImpl.kt in the repository package. This file implements the methods of the interface. Copy the following code into the FitnessRepositoryImpl.kt file:

package com.mongodb.infrastructure.repository
import com.mongodb.MongoException
import com.mongodb.client.model.Filters
import com.mongodb.client.model.UpdateOptions
import com.mongodb.client.model.Updates
import com.mongodb.domain.entity.Fitness
import com.mongodb.domain.ports.FitnessRepository
import com.mongodb.kotlin.client.coroutine.MongoDatabase
import kotlinx.coroutines.flow.firstOrNull
import org.bson.BsonValue
import org.bson.types.ObjectId
class FitnessRepositoryImpl(
private val mongoDatabase: MongoDatabase
) : FitnessRepository {
companion object {
const val FITNESS_COLLECTION = "fitness"
}
override suspend fun insertOne(fitness: Fitness): BsonValue? {
try {
val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).insertOne(
fitness
)
return result.insertedId
} catch (e: MongoException) {
System.err.println("Unable to insert due to an error: $e")
}
return null
}
override suspend fun deleteById(objectId: ObjectId): Long {
try {
val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).deleteOne(Filters.eq("_id", objectId))
return result.deletedCount
} catch (e: MongoException) {
System.err.println("Unable to delete due to an error: $e")
}
return 0
}
override suspend fun findById(objectId: ObjectId): Fitness? =
mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).withDocumentClass<Fitness>()
.find(Filters.eq("_id", objectId))
.firstOrNull()
override suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long {
try {
val query = Filters.eq("_id", objectId)
val updates = Updates.combine(
Updates.set(Fitness::exerciseType.name, fitness.exerciseType),
Updates.set(Fitness::notes.name, fitness.notes),
Updates.set(Fitness::details.name, fitness.details)
)
val options = UpdateOptions().upsert(true)
val result =
mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION)
.updateOne(query, updates, options)
return result.modifiedCount
} catch (e: MongoException) {
System.err.println("Unable to update due to an error: $e")
}
return 0
}
}
4

Paste the following code into the Application.kt file. This code performs the following actions:

  1. Sets up the ContentNegotiation plugin to handle JSON serialization and deserialization by using the Gson formatter

  2. Enables dependency injection by using Koin

  3. Configures the Swagger API route, which is available at the /swagger endpoint

package com.mongodb
import com.mongodb.application.routes.fitnessRoutes
import com.mongodb.domain.ports.FitnessRepository
import com.mongodb.infrastructure.repository.FitnessRepositoryImpl
import com.mongodb.kotlin.client.coroutine.MongoClient
import io.ktor.serialization.gson.gson
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.plugins.swagger.swaggerUI
import io.ktor.server.routing.routing
import io.ktor.server.tomcat.EngineMain
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
import org.koin.logger.slf4jLogger
fun main(args: Array<String>): Unit = EngineMain.main(args)
fun Application.module() {
install(ContentNegotiation) {
gson {
}
}
install(Koin) {
slf4jLogger()
modules(module {
single { MongoClient.create(
environment.config.propertyOrNull("ktor.mongo.uri")?.getString() ?: throw RuntimeException("Failed to access MongoDB URI.")
) }
single { get<MongoClient>().getDatabase(environment.config.property("ktor.mongo.database").getString()) }
}, module {
single<FitnessRepository> { FitnessRepositoryImpl(get()) }
})
}
routing {
swaggerUI(path = "swagger-ui", swaggerFile = "openapi/documentation.yaml") {
version = "4.15.5"
}
fitnessRoutes()
}
}

Open the application.conf file and paste in the following:

ktor {
deployment {
port = 8080
}
application {
modules = [ com.mongodb.ApplicationKt.module ]
}
mongo {
uri = ${?MONGO_URI}
database = ${?MONGO_DATABASE}
}
}

Open the documentation.yaml file and paste in the following:

openapi: 3.0.0
info:
title: Fitness API
version: 1.0.0
description: |
This Swagger documentation file outlines the API specifications for a Fitness Tracker application built with Ktor and MongoDB. The API allows users to manage fitness records including creating new records, updating and deleting records by ID. The API uses the Fitness and FitnessDetails data classes to structure the fitness-related information.
paths:
/fitness:
post:
summary: Create a new fitness record
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FitnessRequest'
responses:
'201':
description: Fitness created successfully
'400':
description: Bad request
/fitness/{id}:
get:
summary: Retrieve fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Successful response
content:
application/json:
example: {}
'404':
description: Fitness not found
delete:
summary: Delete fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: Fitness deleted successfully
'400':
description: Bad request
'404':
description: Fitness not found
patch:
summary: Update fitness record by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FitnessRequest'
responses:
'200':
description: Fitness updated successfully
'400':
description: Bad request
'404':
description: Fitness not found
components:
schemas:
Fitness:
type: object
properties:
id:
type: string
format: uuid
exerciseType:
type: string
notes:
type: string
details:
$ref: '#/components/schemas/FitnessDetails'
required:
- id
- notes
- details
FitnessDetails:
type: object
properties:
durationMinutes:
type: integer
format: int32
distance:
type: number
format: double
caloriesBurned:
type: integer
format: int32
required:
- durationMinutes
- distance
- caloriesBurned
FitnessRequest:
type: object
properties:
exerciseType:
type: string
notes:
type: string
details:
$ref: '#/components/schemas/FitnessDetails'
required:
- exerciseType
- notes
- details

This file defines which methods our API provides through the /swagger-ui endpoint.

5

This step implements the following API endpoints:

  • GET /fitness/{id}: Retrieves a fitness entry by its ID

  • POST /fitness: Creates a new fitness entry

  • PATCH /fitness/{id}: Updates the specified fitness entry

  • DELETE /fitness/{id}: Deletes the specified fitness entry

Create a routes package under the application package and create a file named FitnessRoutes.kt in the routes package. Copy the following code into the FitnessRoutes.kt file:

package com.mongodb.application.routes
import com.mongodb.application.request.FitnessRequest
import com.mongodb.application.request.toDomain
import com.mongodb.domain.ports.FitnessRepository
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.route
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import io.ktor.server.routing.delete
import io.ktor.server.routing.get
import io.ktor.server.routing.patch
import org.bson.types.ObjectId
import org.koin.ktor.ext.inject
fun Route.fitnessRoutes() {
val repository by inject<FitnessRepository>()
route("/fitness") {
post {
val fitness = call.receive<FitnessRequest>()
val insertedId = repository.insertOne(fitness.toDomain())
call.respond(HttpStatusCode.Created, "Created fitness with id $insertedId")
}
delete("/{id?}") {
val id = call.parameters["id"] ?: return@delete call.respondText(
text = "Missing fitness id",
status = HttpStatusCode.BadRequest
)
val delete: Long = repository.deleteById(ObjectId(id))
if (delete == 1L) {
return@delete call.respondText("Fitness Deleted successfully", status = HttpStatusCode.OK)
}
return@delete call.respondText("Fitness not found", status = HttpStatusCode.NotFound)
}
get("/{id?}") {
val id = call.parameters["id"]
if (id.isNullOrEmpty()) {
return@get call.respondText(
text = "Missing id",
status = HttpStatusCode.BadRequest
)
}
repository.findById(ObjectId(id))?.let {
call.respond(it.toResponse())
} ?: call.respondText("No records found for id $id")
}
patch("/{id?}") {
val id = call.parameters["id"] ?: return@patch call.respondText(
text = "Missing fitness id",
status = HttpStatusCode.BadRequest
)
val updated = repository.updateOne(ObjectId(id), call.receive())
call.respondText(
text = if (updated == 1L) "Fitness updated successfully" else "Fitness not found",
status = if (updated == 1L) HttpStatusCode.OK else HttpStatusCode.NotFound
)
}
}
}

The preceding code defines the API endpoints and their handlers.

Before running the application, delete the plugins folder which contains HTTP.kt, Routing.kt, and Serialization.kt files, because we have already included them in the Application.kt file.

6

Retrieve your connection URI from your MongoDB Atlas cluster. For more information on how to do this, see the Connection URI section of the Connect to MongoDB guide.

Open the application.conf file and add the following configuration values:

-DMONGO_URI= <your connection URI>
-DMONGO_DATABASE= <your database name>

Navigate to the Application.kt file in IntelliJ and click the Run button. You can now access the API by navigating to http://localhost:8080/swagger-ui/ in a web browser.

To learn more about Ktor, see the Ktor documentation.

To view the source code of this tutorial, see the Kotlin Ktor MongoDB Vector Search repository on GitHub.

Back

Validate Driver Signatures

On this page