Getting started with REST in Kotlin using Spring Boot

getting-started-rest-kotlin-using-spring-boot-header.png

This article is aimed at showing you how to build backend RESTful APIs in Kotlin using Spring Boot that your client apps can consume.

Introduction

Kotlin is the hot language right now but there is a misconception that, because it runs on the JVM, it’s just for the Java ecosystem. Some web developers (especially those with a JavaScript background) are keeping away from it because they think that it cannot be used for their web projects. This article is aimed at showing you how to build backend RESTful APIs that your client apps can consume.

Assuming that you already know the basics of Kotlin programming language, here are the tools we will use in this article:

  • Java 8: Kotlin is a programming language that compiles to Java Byte Code, and runs on the Java Virtual Machine. We are going to need the latest version of Java to kick things off
  • Spring Boot: A subproject of Spring Framework that allows for fast prototyping of HTTP web applications
  • Gradle: Our build tool and package manager
  • VSCode: Yes, VSCode. Just to show you that you don’t need IntelliJ IDE to build web apps.
  • Postman: For testing API endpoints.

Project Setup

Let’s get started by specifying dependencies and build scripts in our build.gradle file. Create an empty folder and add this file to the root of the project:

1buildscript {
2      ext.kotlin_version = '1.1.2-4'
3      repositories {
4        mavenCentral()
5      }
6      dependencies {
7        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
8        classpath "org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE"
9        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
10      }
11    }
12    apply plugin: 'application'
13    apply plugin: 'kotlin'
14    // "bootRun" command for starting application
15    apply plugin: 'org.springframework.boot'
16    // Bindings between kotlin and spring
17    apply plugin: 'kotlin-spring'
18    repositories {
19      mavenCentral()
20    }
21    dependencies {
22      // Kotlin Dependencies
23      compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
24      compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
25      testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
26      // Spring Dependencies
27      compile("org.springframework.boot:spring-boot-starter-web") {
28        exclude module: "spring-boot-starter-tomcat"
29      }
30      compile "org.springframework.boot:spring-boot-starter-jetty"
31      compile "org.springframework.boot:spring-boot-starter-actuator"
32      // Jackson Dependencies
33      compile "com.fasterxml.jackson.core:jackson-annotations"
34      compile "com.fasterxml.jackson.core:jackson-core"
35      compile "com.fasterxml.jackson.core:jackson-databind"
36      runtime "com.fasterxml.jackson.datatype:jackson-datatype-jdk8"
37      runtime "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
38      runtime "com.fasterxml.jackson.module:jackson-module-kotlin"
39    }
40    task wrapper(type: Wrapper) {
41      gradleVersion = "3.5"
42    }

You can see this file is like npm in Node or Composer in PHP. It manages all your packages with the added build scripts as well.

In summary, the dependencies include Kotlin, Spring Boot, Kotlin Spring, and Jackson. Jackson helps with serializing JSON which is what our app will be receiving as input and responding with as output via HTTP.

The last thing to complete the project setup is to create an entry point for our app. Create ./src/main/kotlin/api/Application.kt file with the following content:

1// ./src/main/kotlin/api/Application.kt
2    package api
3    import org.springframework.boot.SpringApplication
4    import org.springframework.boot.autoconfigure.EnableAutoConfiguration
5    import org.springframework.context.annotation.Bean
6    import org.springframework.context.annotation.Configuration
7
8    // Entry point
9    @EnableAutoConfiguration
10    @Configuration
11    class Application
12
13    fun main(args: Array<String>) {
14        SpringApplication.run(Application::class.java, *args)
15    }

Kotlin doesn’t require you to create a full class just to register an entry main method. With the top-level functions feature, you can just write a class-independent function. The Application class which we will flesh out soon allows you to register your building blocks (eg. controllers).

Your First Controller

Spring Boot uses controllers to define route handlers. When you have a path like GET /comments, a method in a Spring controller is responsible for handling whatever happens when this route is hit.

Let’s add in index route as an example:

1// ./src/main/kotlin/api/AppController.kt
2    package api
3    import java.time.Instant
4    import org.springframework.web.bind.annotation.RequestMapping
5    import org.springframework.web.bind.annotation.RestController
6
7    @RestController
8    class AppController {
9
10        @RequestMapping("/")
11        fun index() = "This is home!"
12    }

Notice how what we got is just a basic class with a simple inline method that returns a string. Just Kotlin stuff. The obvious difference is the annotations made using the Spring RestController annotation on the AppController class and RequestMapping annotation on the method.

RestController annotation tells Spring that this class has members which will handle RESTful resources and should be treated as one, while RequestMapping defines a method as a HTTP handler. It also takes a string argument specifying the route path.

We need to let the entry class know about our new controller, hence:

1class Application {
2        @Bean
3        fun controller() = AppController()
4    }

You can test what we have so far by running the boot command:

1gradle bootRun

Now head to http://localhost:8080 and see for yourself:

JSON Payload

Most times what we want is not a primitive value, but an object payload in the form of JSON. REST uses JSON for both making requests and sending responses. Let’s see how we can go about doing that.

First, create a data class to represent an object:

1// ./src/main/kotlin/api/Models/Comment.kt
2    package api
3
4    import java.time.Instant
5
6    data class Comment(
7            val author: String,
8            val content: String,
9            val created: Instant
10    )

The data class is named Comment with an author, content and created property. The author and content properties are of type String while created is of type Instant (which is the time at creation).

Next, add another route in the AppController to actually handle comment requests on the /comment path:

1// ./src/main/kotlin/api/AppController.kt
2    package api
3    import java.time.Instant
4    import org.springframework.web.bind.annotation.RequestMapping
5    import org.springframework.web.bind.annotation.RestController
6
7    @RestController
8    class AppController {
9
10        @RequestMapping("/")
11        fun index() = "This is home!"
12
13
14        // New route to hand comment request
15        @RequestMapping("/comment")
16        fun getComment() : Comment {
17            val comment = Comment(
18                    author = "codebeast",
19                    content = "I'm so loving Kotlin",
20                    created = Instant.now()
21            )
22            return comment
23        }
24    }

We just added a getComment method which returns an object when hit.

To serialize the JSON sent, you need to configure Jackson. Create an application.yml file in src/resources with the following content:

1spring:
2        jackson:
3            serialization:
4                indent_output: true
5                write_dates_as_timestamps: false
6                write_durations_as_timestamps: false

Now run the application again and visit /comment to see the output:

Making A Post Request

With Spring, you can also receive data from a client. You need to parse this input using the Jackson’s JsonCreator annotation:

1// ./src/main/kotlin/api/Models/Comment.kt
2
3    package api
4    import java.time.Instant
5    import com.fasterxml.jackson.annotation.JsonCreator
6
7    data class Comment(
8            val author: String,
9            val content: String,
10            val created: Instant
11    )
12    // New data class for incoming comments
13    data class NewComment @JsonCreator constructor(
14            val author: String,
15            val content: String
16    )

We added another class and decorated its constructor with @JsonCreator which tells Jackson to parse any input coming through this class as JSON.

Next, add a controller method to handle an incoming POST /content request:

1package api
2    import java.time.Instant
3    import org.springframework.web.bind.annotation.RequestMapping
4    import org.springframework.web.bind.annotation.RestController
5    import org.springframework.web.bind.annotation.RequestBody
6    import org.springframework.web.bind.annotation.RequestMethod
7
8    @RestController
9    class AppController {
10
11        // ...
12        @RequestMapping(value = "/comment", method = arrayOf(RequestMethod.POST))
13        fun createUser(@RequestBody newComment: NewComment): Comment {
14            val comment = Comment(
15                    author = newComment.author,
16                    content = newComment.content,
17                    created = Instant.now()
18            )
19            return comment
20        }
21    }

All this does is send us back what we sent in while adding a created timestamp.

Here is what the request looks like from Postman:

Query Strings and Parameters

You can also pass in query strings to requests:

1package api
2    import java.time.Instant
3    import org.springframework.web.bind.annotation.RequestMapping
4    import org.springframework.web.bind.annotation.RestController
5    import org.springframework.web.bind.annotation.RequestBody
6    import org.springframework.web.bind.annotation.RequestMethod
7    import org.springframework.web.bind.annotation.RequestParam
8
9    @RestController
10    class AppController {
11
12        // ...
13        @RequestMapping("/search")
14        fun search(@RequestParam(name = "name") value: String) : String 
15            = value
16    }

We use the RequestParam annotation to receive a value and then return the value. Simple enough to show how query strings work. Here is an image of the request:

Finally, you can have route parameters on routes using PathVariable annotation:

1package api
2    import java.time.Instant
3    import org.springframework.web.bind.annotation.RequestMapping
4    import org.springframework.web.bind.annotation.RestController
5    import org.springframework.web.bind.annotation.RequestBody
6    import org.springframework.web.bind.annotation.RequestMethod
7    import org.springframework.web.bind.annotation.RequestParam
8    import org.springframework.web.bind.annotation.PathVariable
9
10    @RestController
11    class AppController {
12
13        //...
14        @RequestMapping("/search")
15        fun search(@RequestParam(name = "name") value: String) : String 
16            = value
17        @RequestMapping("/comment/{value}")
18        fun findComment(@PathVariable("value") value: String) : String 
19            = value
20    }

Please find the GitHub repo here.

Conclusion

There is more from Spring and Kotlin — more HTTP methods to handle, data persistence, deploying, etc. You can learn more about this from the Spring Boot website and if you need to brush up your Kotlin skills, Kotlin Koans is a good place to spend some of your time. Let me know in the comments if you run into issues getting started.