How to perform CRUD Operations via API Using Kotlin Micronaut with AWS

Building a CRUD REST API with Kotlin Micronaut, AWS Lambda, API Gateway, DynamoDB, and S3

Introduction

In this blog, we'll walk you through the process of building a CRUD REST API using Kotlin Micronaut, deploying it on AWS Lambda, and integrating it with AWS API Gateway, DynamoDB, and S3.

Pre-requisites

  1. Java JDK 11 or higher (Amazon Corretto recommended)
  2. Micronaut CLI (mn-cli, Latest Version)
  3. AWS Primary Account with root user access
  4. Need to install & configure AWS command line interface named aws-cli

Steps

1. Firstly install & verify Java JDK(Amazon Corretto), Micronaut CLI & AWS CLI

C:\Users\HP>D: D:\>cd Prabha D:\Prabha>mkdir car D:\Prabha>cd car # Verify Java installation D:\Prabha\car>java --version openjdk 11.0.16.1 2022-08-12 LTS OpenJDK Runtime Environment Corretto-11.0.16.9.1 (build 11.0.16.1+9-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.16.9.1 (build 11.0.16.1+9-LTS, mixed mode) # Verify Micronaut installation D:\Prabha\car>mn --version Micronaut Version: 3.7.2 # Verify AWS CLI installation D:\Prabha\car>aws --version aws-cli/2.8.3 Python/3.9.11 Windows/10 exe/AMD64 prompt/off

2. Create a Micronaut Application

Create a new Micronaut application using the Micronaut CLI via Command Prompt:

C:\Users\HP>D: D:\>cd Prabha D:\Prabha>cd car D:\Prabha\car>mn mn> create-app cars --lang kotlin --build gradle --test junit | Application created at D:\Prabha\car\cars mn> exit

Open the project folder in IntelliJ IDEA. Define the base package and create sub-packages: config, model, dao, and controller.

3. Configure Build File

Edit the build.gradle file to set the runtime to "lambda" and add dependencies for DynamoDB and GSON:

micronaut { runtime("lambda") } dependencies { implementation("software.amazon.awssdk:dynamodb-enhanced:2.17.261") implementation("software.amazon.awssdk:dynamodb:2.17.261") implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9' }

4. Configure DynamoDB

Create a DynamoDbConfig.kt class in the config package:

package cars.config import io.micronaut.context.annotation.Bean import io.micronaut.context.annotation.Factory import jakarta.inject.Singleton import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient import software.amazon.awssdk.services.dynamodb.DynamoDbClient @Factory class DynamoDbConfig { @Singleton fun dynamoDBClient(): DynamoDbClient = DynamoDbClient.builder().build() @Bean @Singleton fun dynamoDBEnhancedClient(): DynamoDbEnhancedClient { return DynamoDbEnhancedClient.builder().dynamoDbClient(dynamoDBClient()).build() } }

5. Define Model Class

Create a Car.kt data class in the model package:

package cars.model import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey @DynamoDbBean data class Car( @get:DynamoDbPartitionKey var car_id: String? = null, var car_name: String? = null, var car_brand: String? = null, var car_price: String? = null )

6. Define DAO Class

Create a CarDao.kt class in the dao package:

package cars.dao import cars.model.Car import jakarta.inject.Inject import jakarta.inject.Singleton import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient import software.amazon.awssdk.enhanced.dynamodb.TableSchema import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable @Singleton data class CarDao( @Inject private val dynamoClient: DynamoDbEnhancedClient ) { private val carTable = dynamoClient.table("car_info", TableSchema.fromBean(Car::class.java)) fun save(car: Car) { return carTable.putItem(car) } fun update(car: Car): Car { return carTable.updateItem(car) } fun delete(car: Car): Car { return carTable.deleteItem(car) } fun getById(car: Car): Car { return carTable.getItem(car) } fun getAll(): MutableList<Car> { val carIdList: PageIterable<Car> = carTable.scan() val carIdWholeList = mutableListOf<Car>() carIdList.stream().forEach { fetch -> fetch.items().forEach { pay -> carIdWholeList.add(pay) } } return carIdWholeList } }

7. Define Controller Class

Create a CarController.kt class in the controller package:

package cars.controller import cars.dao.CarDao import cars.model.Car import com.google.gson.GsonBuilder import io.micronaut.http.HttpResponse import io.micronaut.http.annotation.* import jakarta.inject.Inject @Controller("/cars") data class CarController( @Inject private val carDao: CarDao ) { companion object { private val mapper = GsonBuilder().setPrettyPrinting().create() } @Post("/add") fun addCar(@Body car: Car): HttpResponse<String> { carDao.save(car) return HttpResponse.ok("Saved Car Id is ${car.car_id} ok!") } @Put("/update") fun updateCar(@Body car: Car): HttpResponse<String> { carDao.update(car) return HttpResponse.ok("Modified Car Id is ${car.car_id} ok!") } @Delete("/remove") fun deleteCar(@Body car: Car): HttpResponse<String> { carDao.delete(car) return HttpResponse.ok("Removed Car Id is ${car.car_id} ok!") } @Get("/fetch/{id}") fun getCarById(@PathVariable("id") carId: String): HttpResponse<Car> { val car = carDao.getById(Car(carId)) return HttpResponse.ok(car) } @Get("/all") fun getAllCars(): HttpResponse<String> { val carIdList = carDao.getAll() val jsonBody = mapper.toJson(carIdList) return HttpResponse.ok(jsonBody) } }

AWS Setup

1. AWS Lambda

Create a Lambda function and set the runtime to Java 11 (Corretto). Configure the handler to io.micronaut.function.aws.proxy.MicronautLambdaHandler.























2. AWS API Gateway

Create a REST API in API Gateway and configure it to integrate with your Lambda function. Deploy the API and note the Invoke URL.























3. DynamoDB

Create a DynamoDB table to store your car data. Ensure the primary key matches the field annotated with @DynamoDbPartitionKey in your Car class.














4. AWS Cloud Watch

Next, it’s the most important thing to do. Please remember that once we create a new table in DynamoDB then alarms in AWS cloud watch will get turned on and we need to delete it. That is a mandatory step to do or “otherwise we will be charged in Dollars” ok.
For each new table created in DynamoDB totally 8 alarms will get turned on in that there are 6 All Alarms and 2 In Alarms ok.
We need to delete those total (8) Alarms mandatorily or “otherwise we will be charged in Dollars” ok.
Firstly, we need to move towards AWS Cloud Watch by typing Cloud Watch in the Search Bar of our AWS management console as shown below,










5. AWS S3

Create an S3 bucket to store your Micronaut application JAR file.









6. IAM USER

Now, we just need to create a AWS CLI user to access Lambda and S3 via Command Prompt(cmd) with the help of that only we can put a JAR file of the application using the multi-line command.


















Now, just use this command aws configure and set the credentials in your command prompt(cmd) to access S3 and Lambda with the help of AWS CLI for running the multi-line commands ok. Next, use this command aws sts get-caller-identity to verify that whether you correctly set the credentials or not for your respective CLI user.


Deploying and Testing

  1. Build and Upload JAR: Use the following multi-line command to build and upload the JAR file to S3:
gradlew clean && gradlew build && aws s3 cp build/libs/cars-0.1-all.jar s3://your-bucket-name && aws lambda update-function-code --function-name your-lambda-function-name --s3-bucket your-bucket-name --s3-key cars-0.1-all.jar
  1. Test Using Postman: Use the Invoke URL from API Gateway in Postman to test your API endpoints (/cars/add, /cars/update, /cars/remove, /cars/fetch/{id}, /cars/all).

Conclusion

In this tutorial, we have covered the steps to create a CRUD REST API using Kotlin Micronaut, deploy it to AWS Lambda, and integrate it with AWS API Gateway, DynamoDB, and S3. By following these steps, you can efficiently build and deploy serverless applications with robust CRUD capabilities. Happy Coding!

Comments