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 [email protected]. I’d greatly appreciate your feedback! :)