# Serverless Development with Kotlin, AWS Lambda, and MongoDB Atlas

As seen in a previous tutorial, creating a serverless function for AWS Lambda with Java and MongoDB isn't too complicated of a task. In fact, you can get it done with around 35 lines of code!

However, maybe your stack doesn't consist of Java, but instead Kotlin. What needs to be done to use Kotlin for AWS Lambda and MongoDB development? The good news is not much will be different!

In this tutorial, we'll see how to create a simple AWS Lambda function. It will use Kotlin as the programming language and it will use the MongoDB Kotlin driver for interacting with MongoDB.

## The requirements

There are a few prerequisites that must be met in order to be successful with this particular tutorial:

- Must have a Kotlin development environment installed and configured on your local computer.
- Must have a MongoDB Atlas instance deployed and configured.
- Must have an Amazon Web Services (AWS) account.

The easiest way to develop with Kotlin is through IntelliJ, but it is a matter of preference. The requirement is that you can build Kotlin applications with Gradle.

For the purpose of this tutorial, any MongoDB Atlas instance will be sufficient whether it be the M0 free tier, the serverless pay-per-use tier, or something else. However, you will need to have the instance properly configured with user rules and network access rules. If you need help, use our MongoDB Atlas tutorial as a starting point.

## Defining the project dependencies with the Gradle Kotlin DSL

Assuming you have a project created using your tooling of choice, we need to properly configure the **build.gradle.kts** file with the correct dependencies for AWS Lambda with MongoDB.

In the **build.gradle.kts** file, include the following:

```kotlin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.9.0"
    application
    id("com.github.johnrengelman.shadow") version "7.1.2"
}

application {
    mainClass.set("example.Handler")
}

group = "org.example"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test"))
    implementation("com.amazonaws:aws-lambda-java-core:1.2.2")
    implementation("com.amazonaws:aws-lambda-java-events:3.11.1")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
    implementation("org.mongodb:bson:4.10.2")
    implementation("org.mongodb:mongodb-driver-kotlin-sync:4.10.2")
}

tasks.test {
    useJUnitPlatform()
}

tasks.withType {
    kotlinOptions.jvmTarget = "1.8"
}
```

There are a few noteworthy items in the above configuration.

Looking at the `plugins` first, you'll notice the use of Shadow:

```kotlin
plugins {
    kotlin("jvm") version "1.9.0"
    application
    id("com.github.johnrengelman.shadow") version "7.1.2"
}
```

AWS Lambda expects a ZIP or a JAR. By using the Shadow plugin, we can use Gradle to build a "fat" JAR, which includes both the application and all required dependencies. When using Shadow, the main class must be defined.

To define the main class, we have the following:

```kotlin
application {
    mainClass.set("example.Handler")
}
```

The above assumes that all our code will exist in a `Handler` class in an `example` package. Yours does not need to match, but note that this particular class and package will be referenced throughout the tutorial. You should swap names wherever necessary.

The next item to note is the `dependencies` block:

```kotlin
dependencies {
    testImplementation(kotlin("test"))
    implementation("com.amazonaws:aws-lambda-java-core:1.2.2")
    implementation("com.amazonaws:aws-lambda-java-events:3.11.1")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
    implementation("org.mongodb:bson:4.10.2")
    implementation("org.mongodb:mongodb-driver-kotlin-sync:4.10.2")
}
```

In the above block, we are including the various AWS Lambda SDK packages as well as the MongoDB Kotlin driver. These dependencies will allow us to use MongoDB with Kotlin and AWS Lambda.

If you wanted to, you could run the following command:

```bash
./gradlew shadowJar
```

As long as the main class exists, it should build a JAR file for you.

## Developing a serverless function with Kotlin and MongoDB

With the configuration items out of the way, we can focus on the development of our serverless function. Open the project's **src/main/kotlin/example/Handler.kt** file and include the following boilerplate code:

```kotlin
package example

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import com.mongodb.client.model.Filters
import com.mongodb.kotlin.client.MongoClient
import com.mongodb.kotlin.client.MongoCollection
import com.mongodb.kotlin.client.MongoDatabase
import org.bson.Document
import org.bson.conversions.Bson
import org.bson.BsonDocument

class Handler : RequestHandler, Void> {

    override fun handleRequest(input: Map, context: Context): void {

        return null;

    }
}
```

The above code won't do much of anything if you tried to execute it on AWS Lambda, but it is a starting point. Let's start by establishing a connection to MongoDB.

Within the `Handler` class, add the following:

```kotlin
class Handler : RequestHandler, Void> {

    private val mongoClient: MongoClient = MongoClient.create(System.getenv("MONGODB_ATLAS_URI"))

    override fun handleRequest(input: Map, context: Context): void {

        val database: MongoDatabase = mongoClient.getDatabase("sample_mflix")
        val collection: MongoCollection = database.getCollection("movies")

        return null;

    }
}
```

First, you'll notice that we are creating a `mongoClient` variable to hold the information about our connection. This client will be created using a MongoDB Atlas URI that we plan to store as an environment variable. It is strongly recommended that you use environment variables to store this information so your credentials don't get added to your version control.

In case you're unsure what the MongoDB Atlas URI looks like, it looks like the following:

```
mongodb+srv://:@.dmhrr.mongodb.net/?retryWrites=true&w=majority
```

You can find your exact connection string using the MongoDB Atlas CLI or through the MongoDB Atlas dashboard.

Within the `handleRequest` function, we get a reference to the database and collection that we want to use:

```kotlin
val database: MongoDatabase = mongoClient.getDatabase("sample_mflix")
val collection: MongoCollection = database.getCollection("movies")
```

For this particular example, we are using the `sample_mflix` database and the `movies` collection, both of which are part of the optional MongoDB Atlas sample dataset. Feel free to use a database and collection that you already have.

Now we can focus on interactions with MongoDB. Make a few changes to the `Handler` class so it looks like this:

```kotlin
package example

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import com.mongodb.client.model.Filters
import com.mongodb.kotlin.client.MongoClient
import com.mongodb.kotlin.client.MongoCollection
import com.mongodb.kotlin.client.MongoDatabase
import org.bson.Document
import org.bson.conversions.Bson
import org.bson.BsonDocument

class Handler : RequestHandler, List> {

    private val mongoClient: MongoClient = MongoClient.create(System.getenv("MONGODB_ATLAS_URI"))

    override fun handleRequest(input: Map, context: Context): List {

        val database: MongoDatabase = mongoClient.getDatabase("sample_mflix")
        val collection: MongoCollection = database.getCollection("movies")

        var filter: Bson = BsonDocument()

        if(input.containsKey("title") && !input.get("title").isNullOrEmpty()) {
            filter = Filters.eq("title", input.get("title"))
        }

        val results: List = collection.find(filter).limit(5).toList()

        return results;

    }
}
```

Instead of using `Void` in the `RequestHandler` and `void` as the return type for the `handleRequest` function, we are now using `List` because we plan to return an array of documents to the requesting client.

This brings us to the following:

```kotlin
var filter: Bson = BsonDocument()

if(input.containsKey("title") && !input.get("title").isNullOrEmpty()) {
    filter = Filters.eq("title", input.get("title"))
}

val results: List = collection.find(filter).limit(5).toList()

return results;
```

Instead of executing a fixed query when the function is invoked, we are accepting input from the user. If the user provides a `title` field with the invocation, we construct a filter for it. In other words, we will be looking for movies with a title that matches the user input. If no `title` is provided, we just query for all documents in the collection.

For the actual `find` operation, rather than risking the return of more than a thousand documents, we are limiting the result set to five and are converting the response from a cursor to a list.

At this point in time, our simple AWS Lambda function is complete. We can focus on the building and deployment of the function now.

## Building and deploying a Kotlin function to AWS Lambda

Before we worry about AWS Lambda, let's build the project using Shadow. From the command line, IntelliJ, or with whatever tool you're using, execute the following:

```bash
./gradlew shadowJar
```

Find the JAR file, which is probably in the **build/libs** directory unless you specified otherwise.

Everything we do next will be done in the AWS portal. There are three main items that we want to take care of during this process:

1. Add the environment variable with the MongoDB Atlas URI to the Lambda function.
2. Rename the "Handler" information in Lambda to reflect the actual project.
3. Upload the JAR file to AWS Lambda.

Within the AWS Lambda dashboard for your function, click the "Configuration" tab followed by the "Environment Variables" navigation item. Add `MONGODB_ATLAS_URI` along with the appropriate connection string when prompted. Make sure the connection string reflects your instance with the proper username and password.

You can now upload the JAR file from the "Code" tab of the AWS Lambda dashboard. When this is done, we need to tell AWS Lambda what the main class is and the function that should be executed.

In the "Code" tab, look for "Runtime Settings" and choose to edit it. In our example, we had **example** as the package and **Handler** as the class. We also had our function logic in the **handleRequest** function.

With all this in mind, change the "Handler" within AWS Lambda to **example.Handler::handleRequest** or whatever makes sense for your project.

At this point, you should be able to test your function.

On the "Test" tab of the AWS Lambda dashboard, choose to run a test as is. You should get a maximum of five results back. Next, try using the following input criteria:

```json
{
"title": "The Terminator"
}
```

Your response will now look different because of the filter.

## Conclusion

Congratulations! You created your first AWS Lambda function in Kotlin and that function supports communication with MongoDB!

While this example was intended to be short and simple, you could add significantly more logic to your functions that engage with other functionality of MongoDB, such as aggregations and more.

If you'd like to see how to use Java to accomplish the same thing, check out my previous tutorial on the subject titled Serverless Development with AWS Lambda and MongoDB Atlas Using Java.