Create REST services using Ktor
Ktor is a lightweight framework written in Kotlin programming language. This framework allows us to create asynchronous servers and clients. In this article I will explain the basics of Ktor framework by creating a simple REST service. This service will listen on GET, POST, PUT and DELETE requests and perform CRUD database operations. I will use Exposed for database operations.
In this article we are creating a simple REST service with CRUD operations. To develop this application we are going to use Kotlin programming language with below frameworks/libraries.
- Ktor framework
- H2 in-memory database
- Exposed SQL library
Following is the checklist of items we will go through in this article.
- Create Kotlin project
- Add and configure Ktor framework
- DAO layer for database
- Configure REST controllers
Please note that this whole project setup is available in github
Create Kotlin project
First step is to create Kotlin project. Execute below command to create an empty gradle Kotlin project. Once the project is created then import it into your favorite editor.
I executed this command on Gradle 5.2.1 version. You can choose to create kotlin project with any other build tool.
This command will create new project with standard folder structure with default App.kt file.
Add and configure Ktor framework
Adding Ktor framework to the project is easy, just add below dependencies to your build.gradle
Configure Netty embedded server
Open App.kt and update it with below code.
main() function is the starting point of the Kotlin program. We will initialize and start embedded server by passing required information and then we will block main thread such that embedded server can stay live and listen for the requests.
We called embeddedServer function. This function will initiate and run the embedded server. We need to pass some important information to it, they are;
- Server instance type; We passed Netty. Ktor support Jetty and Tomcat as well.
- Port number on which the server will listen.
- Lambda instance of Application. More details about this in next section.
start function make server to start and we passed wait = true to block main thread such that server can listen for the requests.
Now you can run main() function and should see Ktor server startup logs in the console. Server should start with out any errors but we can not test it yet because no end points are defined.
In the previous section a lambda instance which runs with in the context of Application is passed to embeddedServer function. This lambda instance is the where real Ktor framework comes into picture. I am not going into deeper details because it is not in the context of this article, but I will explain the basics.
Ktor works on the concept of features. A Feature is a functionality that we want to put it in the request and response pipeline. Below are some of the examples;
- Logging client requests
- Handling sessions
- Authenticating client requests
Ktor provides many features but they are not included in the pipeline by default. We can customize or use any third party features. In-order to use these features we need to install them. Installing features is so easy, just need to pass the features to the install function. In the below code we installed CallLogging and ContentNegotiation features.
- CallLogging is to log client requests
- ContentNegotiation is used to convert JSON messages. Ktor supports Jackson and GSON but in this article I used Jackson. We can customize this library by passing configuration object but here I am not customizing it.
Once the features are installed then Ktor will listen for the requests. Here we not yet configured end-points so it will not listen for anything. In the next section we will define routing where we will declare some end-points.
Routing will allow us to configure end points of the REST service. In below code I configured a single route;
Looking at the code it understands that we created a parent routing context and with in this we have a single route(‘/employees’). We have get under this route so requests with GET on this route will end up in this block. Another approach to configure routing is using Location annotation. To make it simple I am not using this approach in this tutorial.
call is the instance of the ApplicationCall which is part of the get context and this instance contains properties to access request and response. Here we just reponding with some text ‘Employees test’
Test Ktor server
By now we have routing configured with single endpoint. To make sure everything is working fine, lets start our application and test it. Run main() function and check for no errors in the console log. Open terminal/command prompt and enter below cURL command.
By default curl sends GET request. Response contains ‘Employees test’ means our endpoint is working fine.
DAO layer for database
We are using Exposed to connect and perform SQL operations on H2 in-memory database. We need to include required dependencies in the build.gradle so go to build.gradle and add below dependencies.
To learn more about SQL library Exposed please read this article
Create Model and Mapping
We are using Employee entity as an example so lets create a new package net.thetechstack.model and add Employee.kt with below code.
This is a simple data class with four fields. We want to maintain id, name, email and city for the Employee so the constructor needs all these values.
Create a new package net.thetechstack.dao and add Employees.kt file with below content;
Employees is the mapping object for Employee. This object will give enough information for Exposed to map fields with the database table. This object acts like a object to table relational mapping.
Instead of directly performing database operations we are creating DAO layer. DAO (Data Access Layer) will wrap all database operations in it. Create DAOFacadeDatabase.kt file in net.thetechstack.dao package with below code.
We created an interface, this defines the contract for DAO layer. We added all the methods needed to perform CRUD operations. With in the same class lets implement this interface.
DAOFacadeDatabase needs Database instance. We wrapped all query statements with in the transaction because Exposed needs all the database operations to be performed with in a transaction.
To understand more about Exposed please read this article
Now we have our DAO layer ready.
Integrate Exposed with Ktor
Lets integrate DAO layer with Ktor. We need to instantiate DAOFacadeDatabase by passing H2 in-memory database instance. Below is code;
Instantiated DAOFacadeDatabase by passing H2 database instance and used this instance to create the tables by calling init()
Configure REST controllers
We have DAO and Ktor server configured. Now we will create controller end-points for HTTP requests
Create GET, PUT, POST and DELETE requests
Update routing context with new end-points to handle GET, PUT, POST and DELETE requests.
- GET: returns all employees from the database
- POST: receives Employee JSON and stores in the database
- PUT: updates employee record
- DELETE: deletes the employee for the given id
Test HTTP endpoints with cURL
Start the application by running App.kt and check for no errors in the console. Below are the examples to test end-points with cURL
We performed CRUD operations on the database using REST end-points.
We created a Employee rest service using Ktor and Exposed and then tested all endpoints with some data to verify all database operations.