Skip to main content

Kotlin Server Basics

Introduction​

Kotlin has gained significant traction not just for Android development, but also as an excellent language for server-side applications. With its concise syntax, null safety features, and full Java interoperability, Kotlin offers a modern approach to creating robust backend systems.

This guide will introduce you to the basics of using Kotlin for server-side development, covering fundamental concepts and providing hands-on examples to get you started with your first Kotlin server application.

Why Kotlin for Backend Development?​

Before diving into code, let's understand why Kotlin is becoming increasingly popular for backend development:

  • Concise and expressive syntax: Write less boilerplate code compared to Java
  • Null safety: Minimize runtime exceptions with compile-time null checks
  • Coroutines: Built-in support for asynchronous programming
  • Java interoperability: Seamless integration with existing Java libraries
  • Modern language features: Extension functions, data classes, and functional programming support

Setting Up Your Development Environment​

To get started with Kotlin server development, you need to set up your environment:

  1. Install JDK (Java Development Kit) 8 or newer
  2. Install an IDE like IntelliJ IDEA (Community edition is free)
  3. Install the Kotlin plugin if not already included

Kotlin Server Frameworks Overview​

Several frameworks are available for building servers with Kotlin:

1. Ktor​

A lightweight framework built by JetBrains (creators of Kotlin) specifically designed for creating asynchronous servers and clients.

2. Spring Boot​

A popular Java framework with excellent Kotlin support, offering a comprehensive ecosystem for enterprise applications.

3. Micronaut​

A modern JVM framework designed for building microservices and serverless applications.

4. Javalin​

A simple and lightweight web framework that focuses on simplicity.

5. http4k​

A lightweight but feature-rich HTTP toolkit inspired by Combine from Scala.

For this guide, we'll focus on Ktor as it's designed specifically with Kotlin in mind.

Creating Your First Kotlin Server with Ktor​

Let's build a simple HTTP server with Ktor that responds with "Hello, World!".

Step 1: Set up your project​

Create a new Gradle project and add the necessary dependencies in your build.gradle.kts file:

kotlin
plugins {
kotlin("jvm") version "1.8.0"
application
}

application {
mainClass.set("com.example.ApplicationKt")
}

repositories {
mavenCentral()
}

dependencies {
implementation("io.ktor:ktor-server-core:2.2.4")
implementation("io.ktor:ktor-server-netty:2.2.4")
implementation("ch.qos.logback:logback-classic:1.4.6")
}

Step 2: Create a basic server​

Create a new Kotlin file called Application.kt:

kotlin
package com.example

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Hello, World!")
}
}
}.start(wait = true)
}

Step 3: Run your server​

Execute the main function, and your server will start listening on port 8080. When you visit http://localhost:8080 in your browser, you should see "Hello, World!".

Understanding the Basic Components​

Let's break down the components of our basic Ktor server:

1. Server Engine​

kotlin
embeddedServer(Netty, port = 8080) {
// Configuration and modules
}

This creates an embedded HTTP server using the Netty engine. Ktor supports multiple engines like Netty, Jetty, Tomcat, and CIO (Coroutine I/O).

2. Routing​

kotlin
routing {
get("/") {
call.respondText("Hello, World!")
}
}

The routing block defines HTTP endpoints. Here, we're handling GET requests to the root path ("/") and responding with a text message.

3. Application Call​

The call object represents an HTTP request-response pair and provides methods to access request data and respond to clients.

Handling Different HTTP Methods​

Let's extend our server to handle different HTTP methods:

kotlin
fun main() {
embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondText("Hello from GET!")
}

post("/submit") {
call.respondText("Received POST request")
}

put("/update") {
call.respondText("Received PUT request")
}

delete("/delete") {
call.respondText("Received DELETE request")
}
}
}.start(wait = true)
}

Accepting Request Parameters​

Query Parameters​

kotlin
get("/greet") {
val name = call.parameters["name"] ?: "Guest"
call.respondText("Hello, $name!")
}

When you visit http://localhost:8080/greet?name=John, the server will respond with "Hello, John!".

Path Parameters​

kotlin
get("/users/{id}") {
val userId = call.parameters["id"]
call.respondText("User ID: $userId")
}

A request to http://localhost:8080/users/123 will return "User ID: 123".

Working with JSON​

To handle JSON requests and responses, add the required dependencies:

kotlin
// In build.gradle.kts
implementation("io.ktor:ktor-server-content-negotiation:2.2.4")
implementation("io.ktor:ktor-serialization-jackson:2.2.4")

Then configure your server:

kotlin
import io.ktor.serialization.jackson.*
import io.ktor.server.plugins.contentnegotiation.*
import com.fasterxml.jackson.databind.SerializationFeature

fun main() {
embeddedServer(Netty, port = 8080) {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}

routing {
get("/json") {
call.respond(mapOf("message" to "Hello, JSON!"))
}
}
}.start(wait = true)
}

Accessing http://localhost:8080/json will return:

json
{
"message": "Hello, JSON!"
}

Creating a RESTful API for a Todo Application​

Let's build a simple RESTful API for a Todo application:

kotlin
package com.example

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.jackson.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.concurrent.atomic.AtomicInteger

data class Todo(val id: Int, var title: String, var completed: Boolean = false)

fun main() {
val todos = mutableListOf<Todo>()
val nextId = AtomicInteger(1)

embeddedServer(Netty, port = 8080) {
install(ContentNegotiation) {
jackson()
}

routing {
// Get all todos
get("/todos") {
call.respond(todos)
}

// Get a specific todo
get("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@get
}

val todo = todos.find { it.id == id }
if (todo == null) {
call.respond(HttpStatusCode.NotFound, "Todo not found")
return@get
}

call.respond(todo)
}

// Create a new todo
post("/todos") {
val todoRequest = call.receive<Map<String, String>>()
val title = todoRequest["title"]

if (title.isNullOrBlank()) {
call.respond(HttpStatusCode.BadRequest, "Title is required")
return@post
}

val todo = Todo(nextId.getAndIncrement(), title)
todos.add(todo)
call.respond(HttpStatusCode.Created, todo)
}

// Update a todo
put("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@put
}

val todoIndex = todos.indexOfFirst { it.id == id }
if (todoIndex == -1) {
call.respond(HttpStatusCode.NotFound, "Todo not found")
return@put
}

val todoRequest = call.receive<Map<String, Any>>()
val title = todoRequest["title"] as? String
val completed = todoRequest["completed"] as? Boolean

val todo = todos[todoIndex]
if (title != null) {
todo.title = title
}
if (completed != null) {
todo.completed = completed
}

call.respond(todo)
}

// Delete a todo
delete("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
if (id == null) {
call.respond(HttpStatusCode.BadRequest, "Invalid ID format")
return@delete
}

val removed = todos.removeIf { it.id == id }
if (!removed) {
call.respond(HttpStatusCode.NotFound, "Todo not found")
return@delete
}

call.respond(HttpStatusCode.NoContent)
}
}
}.start(wait = true)
}

This example implements a complete RESTful API with CRUD (Create, Read, Update, Delete) operations for a Todo application.

Request and Response Example​

Creating a new todo (POST request to /todos)

Request:

json
{
"title": "Learn Kotlin Server Development"
}

Response (HTTP 201 Created):

json
{
"id": 1,
"title": "Learn Kotlin Server Development",
"completed": false
}

Getting all todos (GET request to /todos)

Response:

json
[
{
"id": 1,
"title": "Learn Kotlin Server Development",
"completed": false
}
]

Adding Middleware with Ktor Features​

Ktor uses features (also called plugins) to add functionality to your server. Let's see how to add logging and CORS support:

kotlin
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.cors.routing.*
import org.slf4j.event.Level

fun main() {
embeddedServer(Netty, port = 8080) {
// Add logging feature
install(CallLogging) {
level = Level.INFO
}

// Add CORS support
install(CORS) {
anyHost()
allowHeader(HttpHeaders.ContentType)
allowMethod(HttpMethod.Options)
allowMethod(HttpMethod.Get)
allowMethod(HttpMethod.Post)
allowMethod(HttpMethod.Put)
allowMethod(HttpMethod.Delete)
}

// Your routing
routing {
// Routes as before
}
}.start(wait = true)
}

Organizing Your Code with Modules​

As your application grows, it's important to organize your code into modules:

kotlin
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello, World!")
}

// More routes
}
}

fun Application.configureSerialization() {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}
}

fun main() {
embeddedServer(Netty, port = 8080) {
configureSerialization()
configureRouting()
}.start(wait = true)
}

Summary​

In this introduction to Kotlin server basics, we've covered:

  1. Why Kotlin is a great choice for server-side development
  2. Setting up your development environment
  3. Overview of Kotlin server frameworks
  4. Creating a basic HTTP server with Ktor
  5. Handling different HTTP methods
  6. Working with request parameters
  7. Processing JSON requests and responses
  8. Building a RESTful API for a Todo application
  9. Adding middleware with Ktor features
  10. Organizing code with modules

Kotlin provides a modern, concise, and safe language for building server applications. Combined with frameworks like Ktor, it offers an excellent developer experience while producing robust and efficient server applications.

Additional Resources​

Exercises​

  1. Basic Server: Extend the "Hello World" server to include a new endpoint that returns the current date and time.

  2. Parameter Handling: Create a calculator API with endpoints for addition, subtraction, multiplication, and division, taking numbers as query parameters.

  3. Todo API Enhancement: Add functionality to the Todo API to mark todos as completed and to filter todos by completion status.

  4. User Authentication: Implement a basic authentication system using Ktor's Authentication feature.

  5. Database Integration: Extend the Todo application to store todos in a database (such as H2 or PostgreSQL) using a library like Exposed.

Happy coding with Kotlin!



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)