Building a RESTful webservice for MongoDB with Kotlin
I’ve already explained to you how to access MongoDB with Kotlin. Now, we’ll go one step further and expose a MongoDB connection via REST. We’ll build a RESTful webservice to perform the basic CRUD operations on a MongoDB database.
To build our webservice, we’ll use the web framework Ktor from Jetbrains. The easiest way to setup a Ktor project is with the Ktor plugin in IntelliJ (you don’t need IntelliJ or the plugin to follow the example, but it’s the easiest way to develop with Ktor and Kotlin). The plugin is not installed by default, so install it first and then restart IntelliJ.
After installing the plugin, we can create a new project (via File->New->Project) and then select Ktor. Now we can check the features we want to have setup automatically by the project. We’ll only check the following Server features:
Static Content (not really needed, but I always include this in any project)
Status Pages
Routing
Jackson
ContentNegotiation
This will generate a Gradle project where we only need to additionally add the dependency to the Java MongoDB driver.
After generating all this, the file gradle.build looks like this:
Next we create a package com.polyglotphil.data.mongo and create the file MongoDataService in it. Here we’ll setup a connection to a MongoDB instance:
We create a new class that will have a MongoClient and a database name to connect to. With these two members, we create the privatefield database (of class com.mongodb.client.MongoDatabase) with which we’ll work within the class functions.
Note that I already added all the imports that we’ll use in this class, so that I don’t have to mention to do this explicitly every time we add a new class from the Mongo Driver.
The first method that we’ll write, is one to receive all documents from a collection:
We do two things in the function
Retrieve all documents using the MongoDriever
Transform the result to make maps of every single document. For this we’ve written the helper function mongoDocumentToMap which not only transforms the document to a map, but also transform the _id to a String if it is of type ObjectId. I did it to not expose the ObjectId type to the webservice.
Now that we have our first function, let’s expose it via REST. For this, we’ll use the file Applikation.kt that the Ktor plugin created for us. It has following content, after we add our first route:
Of course, the “/static” route, if you don’t want it. I’ve just let it there as the “/static” route may be useful to you if you want to expose some static files (like css, favicon, js or html files).
Let’s have a closer look at the parts that we added:
We added a file local variable mongoDataService and initialized it with a MongoClient connecting to the default host and port (localhost:27017). If you want to change it e.g. to a different port with username/passwort authentication (like I did in my post where I built a Clojure MongoDB Webservice), you just initialize the MongoClient differently:
We added a route to /mongo/example which will return the result of calling mongoDataService.allFromCollection("col"), so we’ll retrieve all documents contained in the collection named col. If you haven’t put anything in yet, you will get an empty result. To test your webservice, run the application (either with gradle run or by executing the main function of the Applikation.kt file). When your webservice runs, you can test it in the browser or with Postman (https://localhost:8080).
Next, we’ll add the CREATE functionality by adding the following function to the MongoDataService:
The function receives a JSON document and stores it in MongoDB. On purpose we remove the any _id property and replace it by one we generate ourselves. If everything goes well, we return the ObjectId of the object we inserted, else we return an error message.
In the Application.kt file, we add the following route, to expose our saveNewDocument function:
If the MongoDataService returns a valid ObjectId, we return HTTP status 201 - Created and the id of the new document in the body. If MongoDataService returns an error message, we return HTTP status 400 - Bad Request.
We can now use Postman to create a few documents and check if our webservice works.
One thing that is still missing, is the possibility to retrieve single documents by id. To achieve this, we need following code in the MongoDataService:
We always return null if we don’t find any element with the corresponding id. Here we explicitly require that the _id property must be an ObjectId. This matches the behaviour we implemented when we add new documents.
Next, we’ll expose it via REST:
We introduced something new here: we used the notation {id} to bind the route parameter, which we can then access with call.parameters["id"].
The next operation to implement is the update operation. Here is the code for the MongoDataService:
Here we return two values: if the change was successful and the error message. We make the following use of them in the application routes:
Finally, in a similar manner, we can implement the delete operation:
Now we have a fully running webservice to insert and update arbitrary JSON values. What we didn’t do here, is validate the input if you want to enforce some schema on your data. We also didn’t add any code, if you want to serialize and deserialize custom Java objects. These points are out of scope for this post, but you should keep them in mind, when you implement a webservice to expose a MongoDB instance.