Introduction to Ktor
Tags: android, api, ktor, kotlin
Published: 2023-05-01 18:14:04

Ktor is a lightweight and flexible web framework for Kotlin, designed to build scalable and high-performance web applications. Its popularity lies in its ease of use and extensibility, allowing developers to quickly develop and deploy applications using a concise and intuitive syntax. Ktor also offers a comprehensive set of features, including support for asynchronous programming, routing, templating, and HTTP clients and servers. Furthermore, Ktor’s compatibility with different environments and systems, such as Android and cloud platforms, has made it a go-to choice for many Kotlin developers.

In this article, we will learn the basics of Ktor and create a simple backend application using Ktor framework.

## Getting Started
### Table of contents
- What is Ktor
- Why Ktor
- Creating a Ktor application
- Understand the project structure
- Create your first Ktor application
- Ktor client on Android

### What is Ktor
Ktor is an open-source framework built by JetBrains for building asynchronous servers and clients using Kotlin programming language.

Ktor is popular because it offers a lightweight and flexible approach to building web applications, with a focus on extensibility and modularization. It allows developers to build web applications quickly and easily, using Kotlin’s concise syntax and functional programming features. It is designed to be highly scalable and performant, making it suitable for building high-traffic web applications. It supports a variety of deployment options, including running on traditional servers, containerized environments, and serverless platforms like AWS Lambda.

### Why Ktor
- **Kotlin and Coroutines**:
Kotlin and Coroutines in Ktor provides a modern and efficient way to build web applications. Kotlin’s expressive syntax and interoperability with Java makes it easier to write and maintain code, while Coroutines enables developers to write efficient and scalable asynchronous code.
- **Lightweight and Flexible**:
Ktor is built using a set of small, focused libraries that can be used independently or together, depending on the needs of the project. It uses Kotlin’s powerful features like extension functions and DSLs (domain-specific languages) to provide a concise and expressive syntax that allows developers to structure their applications freely.
- **Interoperable**:
Ktor is highly interoperable with Java since it built on Kotlin. Ktor can easily integrate with existing Java code and libraries, allowing developers to use the framework in their existing Java projects or with other Java-based frameworks like Springboot.
- **Migration**:
Kotlin enables a smooth transition for large codebases from Java to Kotlin by supporting progressive migration. Developers can write new code in Kotlin while retaining older parts of their system in Java.
- **Tooling support**:
The strong tooling support of Ktor ensures that developers can easily create, manage, and deploy Ktor applications using their preferred tools and workflows. The tight integration with IntelliJ IDEA, in particular, provides a smooth and efficient development experience for Ktor users.
- **Community support**:
Ktor has a strong and supportive community that is dedicated to helping developers learn and use the framework effectively. In addition to the official documentation, the Ktor community also offers support through various channels such as GitHub, StackOverflow, and Reddit.

### Creating a Ktor application
There are multiple ways to start your Ktor projects based on the requirements. You can use [IntelliJ IDEA Ultimate](https://www.jetbrains.com/idea/download/?fromIDE=#section=windows) to create a Ktor project. If you are interested in open-source development, you can use the community version where the web-based project generator can be used to generate a Ktor project. You can also create a new Ktor application via start.ktor.io and add the required functionality via IntelliJ IDEA.

[Create Ktor Applications | ktor.io](https://ktor.io/create/)

In this section, we will use the online Ktor Project Generator tool to create a project.

- Open [https://start.ktor.io/](https://start.ktor.io/) link in your browser.
- Provide a valid project name (Eg: ktor-sample).
- User can see the default project configurations by clicking on the adjust project settings link.

![Project Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhlof86gd6hujfvftgds.png)

- **Project Name**: The name of the project
- **Build System**: The build system to build the project.
- **Website**: The website use to generate project artifact.
- **Artifact**: Read-only generated via the Website you entered.
- **Ktor Version**: Used to specify the version of Ktor.
- **Engine**: Core part used to run the server.
- **Configuration in**: Represents the locations to store server variables.
- **Add Sample code**: Used to add some dummy code to the project.

In this demo, the default project configurations will be used.
- Click on Add plugins buttons to add some features such as Content Negotiation, kotlinx.serialization, Default Headers and Routing.
- **kotlinx.serialization**: ContentNegotiation allows the users to use content converters provided by the kotlinx.serialization library. This library supports JSON, CBOR, ProtoBuf, and other formats.
- **Routing**: Routing is a plugin that is installed into an application to simplify and structure the page request handling. It provides a structured way to handle incoming HTTP requests and generate corresponding responses. With Ktor routing, developers can define routes that match specific request URLs and methods, and then handle these requests with custom logic.
- Click on Generate Project to generate the Ktor project.

![ktor starter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lnziv0naaewgbs6n5o5v.png)

- Download and extract the [project-name].zip (Eg: ktor-sample.zip) file.
- Open it in IntelliJ IDEA community edition and wait for the Gradle sync completion.
- Click on the play icon (▶️) to run the ktor server.

![project1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9sp7d7qt6ltbe3aoqzw.png)

- The Run window shows a message like “Responding at http://127.0.0.1:8080” if the server is running successfully.

![project2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0iit3t33v0zt2zdy6v73.png)

- The “Hello World!” message will be shown if you hit the http://127.0.0.1:8080 URL on the browser.

![output1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/njukn6vg602j39s0po38.png)

### Understand the project structure
The project structure of a Ktor application is fairly flexible and can be customized to meet the needs of the developer. However, there are some common elements that are often included in a Ktor project structure:

![project structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ty4juqikqfipq7y7ih0.png)

- `src` directory: This directory typically contains the source code for the application. It may be further divided into subdirectories based on the modules or features of the application.
- `resources` directory: This directory typically contains any non-source files that are required by the application, such as configuration files, static assets, or templates.
- `test` directory: This directory typically contains unit and integration tests for the application.
- `build.gradle.kts` or `pom.xml`: These files contain the build configuration for the application, including dependencies, plugins, and other settings.
- `application.conf`: This file contains the configuration settings for the Ktor application, such as server settings, logging, and security settings.
- `Application.kt`: This file contains the main entry point for the Ktor application, where the server is configured and started.
Additionally, a Ktor project may include other directories or files based on the user requirements such as a database directory, custom libraries or modules, or additional configuration files.

### Create your first Ktor application
In this section, we will create a custom Ktor application which returns book information to the user. We will continue with the above code (the starter code generated using the Ktor generator) to create the application. If you are interested in creating new applications, the ktor generator or IntelliJ IDEA Ultimatie IDE can be used.

- Open the downloaded project in IntelliJ IDEA community edition and wait for the Gradle sync completion.
- Create a package called `com.example.models` by right clicking on the `com.example` directory, selecting New → Package and provide a valid name called models.
- Create custom routes for create, list and delete books. Create a model for the Book which contains id, title, author, publications. For which, right click on the models folder, select New → Kotlin class / file and provide a valid name called `Book`. Add the following code to it.

```
package com.example.models

import kotlinx.serialization.Serializable

@Serializable
data class Book (
    val id: Int,
    val title: String,
    val author: String,
    val publications: String
)
```
In this section, we will use MutableList type structure for adding books and simulating the data without using any databases.

- **POST(“/book”) — Create a new book**
To create a new book, add a new endpoint as a post request to the `Serialization.kt` file. Add the following code to it.

```
package com.example.plugins

import com.example.models.Book
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.routing.*

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
    val books = mutableListOf<Book>()
    routing {
        get("/json/kotlinx-serialization") {
                call.respond(mapOf("hello" to "world"))
            }
        post("/book") {
            val requestBody = call.receive<Book>()
            books.add(requestBody)
            call.respond(requestBody)
        }
    }
}
```
- **GET(“/books”) — Fetch the list of books**
Open the `Serialization.kt` file, add a new endpoint as a get request to retrieve all books. Follow the below code,

```
package com.example.plugins

import com.example.models.Book
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.routing.*

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
    val books = mutableListOf<Book>()
    routing {
        get("/json/kotlinx-serialization") {
                call.respond(mapOf("hello" to "world"))
            }
        post("/book") {
            val requestBody = call.receive<Book>()
            books.add(requestBody)
            call.respond(requestBody)
        }

        get("/books") {
            call.respond(books)
        }
    }
}
```
To run the app, select the `Application.kt` file and press the play button (▶️). Open the postman and hit the urls (Eg: http://127.0.0.1:8080/book) with a json request body to see the output as below,

![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5f3knn9woqcrp8ovg4u.png)
![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qaode19drrrq4zidj0cq.png)
![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxikqu12zs3uhofqoeso.png)

### Creating service and routing
In the last section, we learned to create a basic get / post requests. In this section, we will create a service and route that preloads book information.

As a first step, comment on the lines you have added in the `Serialization.kt` file as below.

```
package com.example.plugins

import com.example.models.Book
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.routing.*

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
//    val books = mutableListOf<Book>()
    routing {
        get("/json/kotlinx-serialization") {
                call.respond(mapOf("hello" to "world"))
            }
//        post("/book") {
//            val requestBody = call.receive<Book>()
//            books.add(requestBody)
//            call.respond(requestBody)
//        }
//
//        get("/books") {
//            call.respond(books)
//        }
    }
}
```
#### Create the service

Create a package called `com.example.services` and add a new Kotlin class / file named `BookService.kt` in it. Add the following code to it.

```
package com.example.services

import com.example.models.Book;

class BookService {
    private val books = mutableListOf<Book>();
    init {
        books.add(Book(1,"C Programming Language","Kernighan Brian W","Pearson"))
        books.add(Book(2,"Eloquent JavaScript, 3rd Edition: A Modern Introduction to Programming","Marijn Haverbeke","No Starch Press"))
        books.add(Book(3,"Clean Code","Robert C Martin","Pearson"))
        books.add(Book(4,"Python Crash Course, 2nd Edition: A Hands-On, Project-Based Introduction to Programming","Eric Matthes","No Starch Press"))
        books.add(Book(5,"Effective Java","Joshua Bloch","Addison-Wesley Professional"))
        books.add(Book(6,"Introduction to Algorithms: CLRS","Thomson H.Cormen","PHI"))
    }

    public fun getBooks() : List<Book> = books

    public fun getBook(id: Int?) : Book? {
        return books.find { book -> book.id == id }
    }
    public fun addBook(book: Book) : Book? {
        books.add(book)
        return book
    }
    public fun deleteBook(id: Int?) {
        books.removeAll{ book -> book.id == id }
    }
}
```
**Understand the code:**
- Initialize a variable called books of type `mutableList` and prepopulate some values to the list in the constructor.
- `getBooks()` returns all the book information as a list.
getBook() takes id as an argument and returns the specified book information.
- `addBook()` takes book object as an argument and adds it to the list.
- `deleteBook()` deletes the book by the specified id.

#### Create the route
Create a package called `com.example.routing` and add a new Kotlin class / file named `BookRoute.kt` in it. Add the following code to it.

```
package com.example.routing

import com.example.models.Book
import com.example.services.BookService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.lang.Exception

@Suppress("unused")
fun Route.bookRoutes() {

    val bookService = BookService()

    route("books") {
        get {
            call.respond(bookService.getBooks())
        }
        get("/{id}") {
            val bookIdFromQuery = call.parameters["id"] ?: kotlin.run {
                throw Exception("Please provide a valid id")
            }
            val book = bookService.getBook(bookIdFromQuery?.toIntOrNull())
            if (book == null) {
                call.respond(HttpStatusCode.NotFound, "Book not found");
            } else {
                call.respond(book)
            }
        }
        post {
            val requestBody = call.receive<Book>()
            bookService.addBook(requestBody)
            call.respond(requestBody)
        }
        delete("/{id}") {
            val bookIdFromQuery = call.parameters["id"] ?: kotlin.run {
                throw Exception("Please provide a valid id")
            }
            bookService.deleteBook(bookIdFromQuery?.toIntOrNull())
            call.respond("Book is deleted")
        }
    }
}
```
**Understand the code:**
- Create an instance book service as bookService.
- Create a route /books with get, post and delete endpoints.
- The default get route will respond with book information by calling the `getBooks()` method from `bookService`. The other get route takes id as a parameter and returns the specified book using `getBook()` method.
- The post route will accept a book object as the request body content in a json format and will add it to the books list using `addBook()` method.
- The delete route takes `id` as a parameter and deletes the specified book from the list.

#### Add the route
Open the `Routing.kt` file and add the following code to it.

```
package com.example.plugins

import com.example.routing.bookRoutes
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.application.*

fun Application.configureRouting() {
    routing {
        get("/") {
            call.respondText("Hello World!")
        }
        bookRoutes()
    }
}
```
### Test the app
Now, we can test the app with get / post / delete routes. Select the Application.kt file and click on the play button(▶️) to run the app and use the postman to test the URLs.

Some of the testing screenshots are listed below,

![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rtdwdmrxirjccmlpt0w1.png)

### Ktor client on Android
We have learned to create APIs using Ktor. In this section, we will create an android application which uses Ktor client to access APIs.

We will get data from a dummy site (`https://hub.dummyapis.com/employee?noofRecords=10&idStarts=1001`) and access it in the android app using the ktor android client.

![data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qk1za458pazys3xlswu7.png)

Let’s create the app by the following steps.

- Create an android studio project by clicking on the New Project button.
- Click on next in the newly popped window and provide Name and Package Name in the window, click on Finish to complete the project setup.

![project setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ts6fmp3coz9ourbhblw.png)

- Add the following dependencies to the dependencies block in the app’s `build.gradle` file.

```
ext.ktor_version='2.3.0'

dependencies {
    ...
    implementation("io.ktor:ktor-client-android:${ktor_version}")
    implementation("io.ktor:ktor-client-core:${ktor_version}")
    implementation("io.ktor:ktor-client-cio:${ktor_version}")
    implementation("io.ktor:ktor-client-logging:${ktor_version}")
    implementation("io.ktor:ktor-client-content-negotiation:${ktor_version}")
    implementation("io.ktor:ktor-serialization-kotlinx-json:${ktor_version}")
    implementation("io.ktor:ktor-client-serialization:${ktor_version}")
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1'
}
```
- Add `kotlinx-serialization` in plugins list on app’s `build.gradle` file.

```
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlinx-serialization'
}
```
- Add internet permission to the `AndroidManifest.xml` file.

```
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        ...>
        ...
    </application>
</manifest>
```
- Right click on the package folder (`com.vsoft.ktordemoapp`). Select New → Kotlin Class/File option and create a file named `User.kt`. Add the following code to it.

```
package com.vsoft.ktordemoapp

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class User(
    @SerialName("id")
    val id: String,
    @SerialName("firstName")
    val firstName: String,
    @SerialName("lastName")
    val lastName: String,
    @SerialName("email")
    val email: String,
    @SerialName("contactNumber")
    val contactNumber: String,
    @SerialName("age")
    val age: Int,
    @SerialName("dob")
    val dob: String,
    @SerialName("salary")
    val salary: Float,
    @SerialName("address")
    val address: String,
)
```
The user data class is created as a serializable object which is used to convert the Json object into a kotlin object.

- Open `activity_main.xml` and add an `android:id` field to the textview as below.
    
```
<TextView
        android:id="@+id/user_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
```
- Open `MainActivity.kt` file and add the following code to it.

```
package com.vsoft.ktordemoapp

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.android.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking { getData() }
    }

    suspend fun getData() {
        val client = HttpClient(Android) {
            install(ContentNegotiation) {
                json(Json {
                    prettyPrint = true
                    isLenient = true
                    ignoreUnknownKeys = true
                })
            }
            install(Logging) {
                level = LogLevel.ALL
            }
        }
        var users : Array<User> = client.get("https://hub.dummyapis.com/employee?noofRecords=10&idStarts=1001").body()
        var res = ""
        users?.map { user -> res += user?.id + ", " + user?.firstName + " " + user?.lastName + ", " + user?.age + ", " + user?.salary + "\n" }
         val info = findViewById<TextView>(R.id.user_info);
         info.text = res
         println(res)
    }
}
```
`getData()` is a suspend method which creates an android HTTPClient of ktor (client). It is installed with `ContentNegotiation` to deal with the content type of the request and response data. Logging module is used to list the logs while running the ktor client. The `client.get()` method is used to get data from the URL that we specified as an argument. The `get()` method returns the specified object if you piped it with `body()`.

### Test the app
Click on the play button(▶️) to run the app.

![play](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/buef4dbuytnj2ejnts8q.png)

You get an output as follows,

![output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cj5yzupw58wml34hn702.png)

There you have it! Your first ktor android client app in Android studio :)

Thanks for reading this article.

Thanks Gowri M Bhatt for reviewing the content.

If you enjoyed this article, please click on the heart button ♥ and share to help others find it!

The full source code for this tutorial can be found here,

[GitHub - codemaker2015/ktor-android-client-demo | github.com](https://github.com/codemaker2015/ktor-android-client-demo)

The article is also available on [Medium](https://medium.com/@codemaker2016/introduction-to-ktor-6e238fa20c3).

Here are some useful links,

- [Creating a new Ktor project | ktor.io](https://ktor.io/docs/intellij-idea.html)
- [Content negotiation and serialization | ktor.io](https://ktor.io/docs/serialization-client.html#receive_data)
- [A Guide To Ktor With MongoDB | codersee.com](https://codersee.com/a-guide-to-ktor-with-mongodb/)
- [Introduction to Kotlin Ktor | Ktor](https://www.section.io/engineering-education/introduction-to-kotlin-ktor/)