Level: Beginner
In the previous lesson we made our first GET and POST requests and we used a mutable list of fruits to simulate adding and retrieving data from a database, in this lesson we are going to save our data in a real database as this will be much more convenient 🙂
What is MongoDB?
MongoDB is a popular NoSQL database that is designed for storing and retrieving large volumes of unstructured and semi-structured data. Unlike traditional SQL databases, MongoDB uses a document-based data model, where data is stored in flexible and dynamic JSON-like documents instead of tables and rows.
Document based model?
In MongoDB, data is stored as documents, which are similar to JSON objects. Each document is a self-contained unit of data that can contain multiple fields, arrays, and nested objects. Documents are stored in collections, which are similar to tables in a traditional SQL database.
Unlike traditional SQL databases, MongoDB does not use a fixed schema or structure for storing data. This means that documents within the same collection can have different fields and data types, and fields can be added or removed from documents at any time.
Documents in MongoDB are identified by a unique key called the “_id” field. This field is automatically generated by MongoDB when a new document is inserted into a collection, but it can also be manually specified by the developer.
Prerequisites
- MongoDB server (community version is free)
- MongoDB compass (optional – to view our database and see documents we add)
On June 28, 2023, Mongodb released an official Kotlin driver and the author of Kmongo deprecated it in favor of the Official Kotlin driver, therefore i will continue my future lessons with the official Kotlin driver instead of Kmongo. don’t worry most of the apis and functions are smiliar and migration is easy and i will try to update old lessons to the best of my ability, you can see implementation for both Kmongo and the official driver for the older posts.
Dependencies
For this tutorial im going to use the same project from the previous lesson.
- Kotlin Driver (Official)
- KMongo
Open the build.gradle file in the root of your project and add the following dependencies for Kotlin Driver (latest version here)
dependencies {
// mongodb
implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.2.0")
implementation("org.mongodb:bson-kotlinx:5.2.0")
}
Creating the database
In the “data” package create a new package called “db” then create a new kotlin file called Database, we will define our MongoDB instance and our collections here, off course you can replace “tahaben_db” with whatever you would like to name your database.
- Kotlin Driver (Official)
- KMongo
Open the build.gradle file in the root of your project and add the following dependencies for Kotlin Driver (latest version here)
import com.mongodb.kotlin.client.coroutine.MongoClient
// the "mongodb://localhost:27017" is the connection string to the mongodb community
// server we installed, this is the default string, you can find it by
// opening 'Mongodb Compass' and pressing 'add new connection'
val db = MongoClient.create("mongodb://localhost:27017").getDatabase("tahaben_db")
Now let’s think about what collections do we need, since we’re going to work with a list of fruits we will need a fruit collection, before we define our fruit collection let’s make a small edit to our Fruit data class, we will mark the id attribute with “@BsonId”, since MongoDB needs and id for each item lets use our fruit id to be that id, this is not necessary as MongoDB will automatically create a “_id” field if we don’t specify it but that will just add an unnecessary extra value to each fruit we add to our collection as we will have both “id” and “_id” for each fruit. Another thing since “@BsonId” does not support serialization we will fix this by providing a default value of “ObjectId” to the id, this way serialization will work fine and an id will be auto generated if we don’t specify it.
- Kotlin Driver (Official)
- KMongo
@SerialName("_id")
val id: String = ObjectId().toString()
@BsonId
val id: String = ObjectId().toString()
now back to our Database.kt file let’s define our fruit collection.
- Kotlin Driver (Official)
- KMongo
val fruits = db.getCollection<Fruit>("fruit")
val fruits = db.getCollection<Fruit>()
Now that we defined our collection we would like to perform some actions on it like getting a list of fruits and adding a new fruit we can do that by implementing functions in the same file, we use the find function to return all documents in the collection then we call toList to convert it to a kotlin list, to insert items we use insertOne function and we pass the fruit that we want to add, also wasAcknowlendged will return true if the fruit was added successfully to the database or false if there is an error.
suspend fun addFruit(newFruit: Fruit): Boolean {
return try{
// return true if insertion was successful
fruits.insertOne(newFruit).wasAcknowledged()
}catch (ex: Exception){
ex.printStackTrace()
// return false if there is an error EX: item with same id already exists
false
}
}
suspend fun getFruits(): List<Fruit> {
return fruits.find().toList()
}
To use our new functions in our API we need to go to the FruitRoutes.kt file and change the logic in our endpoints to use them.
fun Route.fruitRoutes() {
get("/fruits/{id?}") {
// get fruits from the database
call.respond(HttpStatusCode.OK, getFruits())
}
post("/add-fruit") {
try {
// receive the fruit from the user
val newFruit = call.receive<Fruit>()
// add the received fruit to the database
if (!addFruit(newFruit)){
// if not added successfully return with an error
return@post call.respond(HttpStatusCode.Conflict, SimpleResponse(success = false, message = "Item already exits"))
}
// acknowledge that we successfully added the fruit by responding
call.respond(
HttpStatusCode.Created, SimpleResponse(
true,
"Successfully added ${newFruit.name}"
)
)
}catch (ex: Exception){
call.respond(HttpStatusCode.BadRequest, SimpleResponse(false,"Invalid Fruit format"))
}
}
}
Start the MongoDB server and try our end points in postman
Open MongoDB Compass find your database name and check out the items you inserted
Deleting and updating data
To delete and update items in our database we can use updateOneById and deleteOneById functions, here is an example of deleting and updating fruits
suspend fun deleteFruit(fruitId: String): Boolean{
return try {
fruits.deleteOneById(fruitId).deletedCount == 1L
}catch (ex: Exception){
ex.printStackTrace()
false
}
}
suspend fun updateFruit(updatedFruit: Fruit): Boolean {
return try {
fruits.updateOneById(id = updatedFruit.id ,update = updatedFruit).wasAcknowledged()
}catch (ex: Exception){
ex.printStackTrace()
false
}
}
And we define the DELETE and PATCH end points in FruitRoutes.kt
patch("/add-fruit") {
try {
val newFruit = call.receive<Fruit>()
if (!updateFruit(newFruit)) {
return@patch call.respond(
HttpStatusCode.NotFound,
SimpleResponse(success = false, message = "Please check the fruit id")
)
}
call.respond(
HttpStatusCode.OK, SimpleResponse(
true,
"Successfully updated ${newFruit.name}"
)
)
} catch (ex: Exception) {
call.respond(HttpStatusCode.BadRequest, SimpleResponse(false, "Invalid Fruit format"))
}
}
delete("/delete-fruit/&{id?}") {
try {
val id = call.parameters["id"]
?: return@delete call.respond(
HttpStatusCode.BadRequest,
SimpleResponse(success = false, message = "Parameter id is required")
)
if (!deleteFruit(id)) {
return@delete call.respond(
HttpStatusCode.NotFound,
SimpleResponse(success = false, message = "Please check the fruit id")
)
}
call.respond(
HttpStatusCode.OK, SimpleResponse(
true,
"Successfully deleted ${id}"
)
)
} catch (ex: Exception) {
call.respond(HttpStatusCode.BadRequest, SimpleResponse(false, "Invalid Fruit format"))
}
}
Give our new end points a try, the update endpoint is the same as add but don’t forget to change the request type to PATCH in post man before you try it out, the delete end point is diffrent and requires an id try it with and without a parameter and notice how the server returns BadRequest.
That’s it for this tutorial! in the next tutorial we are going to learn how to make different queries to MongoDB.
The source code for this tutorial is available here.
[…] back!, in the previous lesson we learned how to setup our mongoDB and how to add, read, update and delete documents from our […]