Kotlin and Spring Boot: Getting Started
Learn how to use Spring Boot to create a web application with the help of Spring Initializr, build a REST API and test it. By arjuna sky kok.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Kotlin and Spring Boot: Getting Started
25 mins
- Getting Started
- Running the Project
- Creating a Controller
- Creating a Model
- Supporting a JSON GET Request
- Supporting a JSON POST Request
- Mapping Path Variables
- Handling Exceptions
- Middleware
- Adding Configuration
- Requesting Mapping
- Testing
- Writing Your First Test
- Writing Your Second Test
- Where To Go From Here?
Spring is one of the most famous web frameworks for web developers. Many developers choose Spring because it’s powerful and supports Dependency Injection and Aspect-Oriented Programming. However, some developers dislike the Spring framework because it’s a Java web framework bloated with many XML configuration files.
Pivotal created Spring Boot, a project created on top of the Spring framework to address those criticisms. It’s opinionated and assumes how you develop web applications. Spring Boot also uses auto-configuration to set up your web application.
In other words, you no longer have to write a lot of XML configuration files. Fortunately, Spring Boot also supports Kotlin and Gradle, which means you don’t have to use Java and write XML files. Yeah! :]
In this tutorial, you’ll build an NFT marketplace because everyone is excited about NFTs. Throughout this process, you’ll:
- Initialize a Spring Boot project.
- Run the web application.
- Write controller, model, configuration, exception and middleware.
- Write and run tests.
Getting Started
You got a new job as a web developer at a startup. Your CEO asks you to build an NFT marketplace. She wants to go mobile-first, so you need to build API endpoints.
Your colleagues need the REST API to build a mobile app to trade NFT. You choose Spring Boot because, well, you read this tutorial. :]
To create a new Spring Boot project, head to Spring Initializr, a website for initializing your Spring Boot project:
Choose Gradle Project in Project. Then select Kotlin as the Language. The default version in the Spring Boot will work just fine.
In the Project Metadata section, set com.raywenderlich in Group. Then fill nftmarketplace in Artifact and Name. Type The NFTs Marketplace in Description.
Use com.raywenderlich.nftmarketplace for Package name. Leave the default values for Packaging and Java.
Then, click Add in the Dependencies section:
The website will present a modal dialog where you can search for any Spring dependencies. For example, if you need a database, this is where you’d select the related component.
Select Spring Web since it comes with a web server where you’ll deploy the web application:
The final screen looks like this:
Click Generate at the bottom of the page. Your browser will download the project in a zip file:
Download the file and extract it. That’s your starter project directory.
To work on this project, you need IntelliJ IDEA. You can learn how to install and set it up on the official IntelliJ IDEA website. Once you’re setup, move onto the next section.
Running the Project
Launch IntelliJ IDEA and open the starter project. Gradle will start syncing the project, which could take a couple of minutes.
In your Spring Boot project, you’ll find the web application code in the src
folder. You’ll write the Kotlin code inside the main/kotlin
folder.
Before you start writing the code for your web application, open build.gradle.kts. This is your Spring Boot Gradle build file where you’ll install the web application’s dependencies and change the project’s configuration:
In the file, you’ll find the Spring Boot plugin inside the plugins
block and further down the Java version for this project. The org.springframework.boot:spring-boot-starter-web
dependency is inside the dependencies
block. Remember you added the Spring Web component in Spring Initializr.
Time to run the web application and see what it looks like. Open NftmarketplaceApplication.kt:
Run the web application by clicking the green play button on the left side of the main function.
A pop-up dialog will appear. Click Run ‘NftmarketplaceApplic…’:
As you can see in the output on the run tool window, IntelliJ IDEA runs the code:
Your Spring project comes with an embedded Tomcat web server, which runs on port 8080. So what are you waiting for? Open the URL http://localhost:8080 in a web browser and you’ll see:
Unlike Django or Rails, Spring Boot doesn’t come with a default index page. It just gives you a 404 NOT FOUND
page.
So, it’s time to work on your web application.
Creating a Controller
First, you’ll build a controller, specifically a REST controller. Under com.raywenderlich.nftmarketplace
, create a package named controller
by right-clicking it and then choosing New ▸ Package:
A pop-up dialog will appear. Add controller
to the filled-in string:
Then, under the controller
directory, create a new file named NftmarketplaceController.kt by right-clicking controller
and choosing New ▸ Kotlin Class/File:
In the pop-up dialog, type NftmarketplaceController. Then make sure Class
is selected before pressing Enter:
You’ll get an empty class:
package com.raywenderlich.nftmarketplace.controller
class NftmarketplaceController {
}
Replace this empty class with:
package com.raywenderlich.nftmarketplace.controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController // 1
class NftmarketplaceController {
@GetMapping("/homepage") // 2
fun getHomePage() = "NFTs Marketplace"
}
Here’s a code breakdown:
-
First, you annotate the class with the
@RestController
annotation. Spring Boot will scan it and recognize it as a controller class. Auto-configuration in Spring Boot means no need to write an XML file manually to register your controller. -
To map a GET request to a method, you annotate the method with
@GetMapping
passing the"/homepage"
path as parameter. In this example, the method returns a string.
Rerun the project by clicking Run:
A pop-up dialog will appear asking if you’re sure you want to rebuild the project. Click Stop and Rerun:
Open http://localhost:8080/homepage. This time you get a text displayed on the web page:
In addition to getting data from the browser, you can also use curl:
curl http://localhost:8080/homepage
You’ll get:
NFTs Marketplace%
Instead of just simple strings, a REST API needs to return data in JSON format. But before that, you’ll create a model to represent the NFT.
Creating a Model
Create a new package under com.raywenderlich.nftmarketplace
and name it model
. Then create a new class under model
and name it NFT
. Replace the contents of the file with:
package com.raywenderlich.nftmarketplace.model
data class NFT(val id: Int, var name: String, var floor_price: Double)
The id
and name
are self-explanatory. The floor_price
is an NFT’s cheapest price in the particular NFT’s collection.
Supporting a JSON GET Request
Go back to the controller code, NftmarketplaceController.kt. Create sample data of NFTs. Then, add the following code inside the class:
private var NFTs = mutableListOf(
NFT(1, "CryptoPunks", 100.0),
NFT(2, "Sneaky Vampire Syndicate", 36.9),
NFT(3, "The Sevens (Official)", 0.6),
NFT(4, "Art Blocks Curated", 1.1),
NFT(5, "Pudgy Penguins", 2.5),
)
Then import:
import com.raywenderlich.nftmarketplace.model.NFT
You don’t need to do anything special to return this data in JSON format. Simply return the objects. Add the following method inside the class:
@GetMapping("")
fun getNFTs() = NFTs
Then import:
import org.springframework.web.bind.annotation.GetMapping
Spring Boot will serialize your mutable list of data class instances to a JSON string behind the scenes. Look at build.gradle.kts and you’ll notice a JSON library, jackson-module-kotlin
:
You didn’t install it explicitly, but it’s included when you install the Spring Web component.
Rerun the project and open http://localhost:8080:
The web application displays NFTs in JSON format.
Executing a curl command:
curl http://localhost:8080
You’ll get:
[{"id":1,"name":"CryptoPunks","floor_price":100.0},{"id":2,"name":"Sneaky Vampire Syndicate","floor_price":36.9},{"id":3,"name":"The Sevens (Official)","floor_price":0.6},{"id":4,"name":"Art Blocks Curated","floor_price":1.1},{"id":5,"name":"Pudgy Penguins","floor_price":2.5}]
You need a method to add a new NFT to your mutable list. To do that, you’ll need to support a JSON post request.
Supporting a JSON POST Request
To support this type of request, you’ll need the @PostMapping
annotation. Create a method inside the controller class:
@PostMapping("") // 1
@ResponseStatus(HttpStatus.CREATED) // 2
fun postNFT(@RequestBody nft: NFT): NFT { // 3
val maxId = NFTs.map { it.id }.maxOrNull() ?: 0 // 4
val nextId = maxId + 1 // 5
val newNft = NFT(id = nextId, name = nft.name, floor_price = nft.floor_price) // 6
NFTs.add(newNft) // 7
return newNft
}
You’ll need these imports:
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.ResponseStatus
Here’s what you did:
-
First, you annotate the method with
@PostMapping
to indicate this method handles a POST request. You use the same path,""
, as the previous GET request. That’s not a problem because one path can serve two different requests. -
The method argument with the
@RequestBody
is the JSON object you’ll send to this path. - Then you get the greatest id of the existing NFTs.
- You calculate the id for the new NFT.
-
Then you create a new NFT with the calculated id, the
name
andfloor_price
, both from the request body. - Finally, you add the new NFT to the list and return it as the response.
Then you add @ResponseStatus
with the HttpStatus.CREATED
argument because, typically, after creating a resource like a new NFT, you emit the 201 CREATED
response status. If you omit this annotation, you’ll get a default 200 OK
response status.
Rerun the project and create a new NFT executing this curl request:
curl -H "Content-Type: application/json" --request POST --data '{"name": "Cryptoadz", "floor_price": 26}' http://localhost:8080
And you’ll get:
{"id":6,"name":"Cryptoadz","floor_price":26.0}
Check the full list of NFTs by sending a GET request to http://localhost:8080
:
curl http://localhost:8080
This is the result:
[{"id":1,"name":"CryptoPunks","floor_price":100.0},{"id":2,"name":"Sneaky Vampire Syndicate","floor_price":36.9},{"id":3,"name":"The Sevens (Official)","floor_price":0.6},{"id":4,"name":"Art Blocks Curated","floor_price":1.1},{"id":5,"name":"Pudgy Penguins","floor_price":2.5},{"id":6,"name":"Cryptoadz","floor_price":26.0}]
Mapping Path Variables
Checking one NFT from a list of a couple of NFTs isn’t a problem. But sometimes, you want to get one NFT’s information.
Create a new GET path by adding the following method to the class:
@GetMapping("/{id}") // 1
fun getNFTById(@PathVariable id: Int) : NFT? { // 2
return NFTs.firstOrNull { it.id == id } // 3
}
Add the following import:
import org.springframework.web.bind.annotation.PathVariable
Here’s a code breakdown:
-
This time, inside the
@GetMapping
annotation, you have anid
parameter between curly braces. -
Because you also have a method parameter
id: Int
annotated with the@PathVariable
annotation, your method receives that parameter whenever the server receives a GET request to/1
,/2
or any number. Notice the return type is an optionalNFT
. -
You try to find the NFT with the desired id and return it. If it doesn’t exist, you’ll return
null
.
Rerun the project and execute the following curl requests:
curl http://localhost:8080/1
You’ll get:
{"id":1,"name":"CryptoPunks","floor_price":100.0}
Now, execute:
curl http://localhost:8080/100
Notice you don’t get any response since the NFT with id=100
doesn’t exist.
Now, try executing with the verbose option:
curl http://localhost:8080/100 -v
You’ll get:
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /100 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Length: 0
< Date: Sat, 30 Oct 2021 22:41:49 GMT
<
* Connection #0 to host localhost left intact
* Closing connection 0
As you can see, the service responds 200 OK
, even if the NFT doesn't exist.
A better way to show the NFT doesn't exist is to return a 404 NOT FOUND
result. You'll do that in the next section.
Handling Exceptions
Now, you'll create a custom exception for your missing NFT case.
Under com.raywenderlich.nftmarketplace
, create a package named exception
. Then inside the package, create the NFTNotFoundException
class and replace the file's content with:
package com.raywenderlich.nftmarketplace.exception
class NFTNotFoundException : Exception()
Inside the same package, create the ControllerAdvice
class. Name it NFTErrorHandler
and replace the file's content with:
package com.raywenderlich.nftmarketplace.exception
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import javax.servlet.http.HttpServletRequest
@ControllerAdvice // 1
class NFTErrorHandler {
@ExceptionHandler(NFTNotFoundException::class) // 2
fun handleNFTNotFoundException(
servletRequest: HttpServletRequest,
exception: Exception
): ResponseEntity<String> {
return ResponseEntity("NFT not found", HttpStatus.NOT_FOUND) // 3
}
}
Here's what's happening:
-
When you annotate the class with
@ControllerAdvice
, Spring Boot scans and registers this class asControllerAdvice
for your controller. -
You annotate the method with
@ExceptionHandler
, which accepts the exception class you created, letting Spring Boot know that this method can handle that exception. -
You return a
ResponseEntity
. The first argument ofResponseEntity
is the result you send to the client which can be a simple text string or a JSON string. The second argument is the status type.
Your job isn't finished. Go back to the controller and replace getNFTById
with:
import com.raywenderlich.nftmarketplace.exception.NFTNotFoundException
...
@GetMapping("/{id}")
fun getNFTById(@PathVariable id: Int): NFT {
val nft = NFTs.firstOrNull { it.id == id }
return nft ?: throw NFTNotFoundException()
}
Now, instead of returning null
, you throw an exception. Therefore, if you try to retrieve an NFT that doesn’t exist, you'll get a 404 NOT FOUND
result.
Rerun the project and go to http://localhost:8080/100:
Or executing curl.
curl http://localhost:8080/100 -v
You'll see this result:
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /100 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 404
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 13
< Date: Sun, 31 Oct 2021 05:03:15 GMT
<
* Connection #0 to host localhost left intact
NFT not found* Closing connection 0
Middleware
You got a feature request from your CEO. She wants to log the UTM marketing flag, so you can reward people who promote your NFT marketplace.
You could modify all of your controller's methods, but that would be too cumbersome. Instead, use middleware.
Under com.raywenderlich.nftmarketplace, create a new package named middleware
. Inside middleware
, create a class named RequestLoggingFilter
and replace the content of the file with:
package com.raywenderlich.nftmarketplace.middleware
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import javax.servlet.Filter
import javax.servlet.FilterChain
import javax.servlet.ServletRequest
import javax.servlet.ServletResponse
@Component
class RequestLoggingFilter : Filter {
val loggerFactory = LoggerFactory.getLogger("NFT Logger")
override fun doFilter(
servletRequest: ServletRequest,
servletResponse: ServletResponse,
filterChain: FilterChain
) {
val utmSource = servletRequest.getParameter("utm_source")
loggerFactory.info("Logging UTM source: $utmSource")
filterChain.doFilter(servletRequest, servletResponse)
}
}
To execute code automatically inside middleware, you create a Filter
class and annotate it with @Component
so Spring Boot can register your class in middleware.
To add code inside middleware, you create doFilter
, which accepts a request, a response and a filter chain. You get the UTM parameter from the request object. In this use case, you don't alter the response, but you could if you wanted to.
Rerun the application and execute:
curl "http://localhost:8080/1?utm_source=raywenderlich"
You'll get this response:
{"id":1,"name":"CryptoPunks","floor_price":100.0}
Check the logging output on the run tool window:
Adding Configuration
Your CEO got another idea. Now she wants to build a white-label NFT marketplace application. You know: In a gold rush, sell shovels.
The idea is simple: You sell your web application to anyone who wants to build an NFT marketplace.
To do that, you need to display a company name flexibly. You can't embed the company code in your code because you want to sell your web application to many companies. That's where the configuration comes in.
You wrote code inside the main folder, specifically inside kotlin
. But main
has another child folder as well, resources
:
resources
has static, which contains static files like images, templatethat contains HTML files and application.properties. This file is where you put configuration.
Open application.properties and add:
company_name=OpenSky
The variable is on the left, and the value is on the right. They're separated by =
.
To use this configuration in the controller, you use the @Value
annotation and bind it to a state variable inside the class. Add the state variable inside the controller class:
@Value("\${company_name}")
private lateinit var name: String
Don't forget to import Value
:
import org.springframework.beans.factory.annotation.Value
@Value
accepts company_name
interpolated inside a string. Spring Boot will bind the resulted value to name
.
Change getHomePage
to use name
:
@GetMapping("/homepage")
fun getHomePage() = "$name: NFTs Marketplace"
Rerun the project and execute this command:
curl http://localhost:8080/homepage
You'll get this output:
OpenSky: NFTs Marketplace
Here, you put configurations in one place. In addition to the company name, you can include information like the database configuration and the network configuration.
Requesting Mapping
Your CEO got another inspiration: Instead of going mobile-first, now she wants a traditional web application that displays HTML pages as well. So you must prepend the API path with /nfts
so http://localhost:8080/2
becomes http://localhost:8080/nfts/2
. You reserve the root URL for the traditional web application.
Easy peasy, right? You could do string substitution, but there’s a better way.
Annotate the controller with @RequestMapping
. Then add the following code above the controller class declaration:
@RequestMapping("/nfts")
Then import:
import org.springframework.web.bind.annotation.RequestMapping
Now, Spring Boot prepends all API paths with /nfts
. To test it out, rerun the project and execute:
curl http://localhost:8080/nfts/1
This is the response:
{"id":1,"name":"CryptoPunks","floor_price":100.0}
Testing
You have a working controller. Good!
Do you know what would be nice? Tests!
You'll put your test files in... the tests
folder. You'll find it inside src
, which is parallel to main
.
You find a simple test file there, called NftmarketplaceApplicationTests. It looks like the following:
package com.raywenderlich.nftmarketplace
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class NftmarketplaceApplicationTests {
@Test
fun contextLoads() {
}
}
In this test file, the test class is annotated with @SpringBootTest
. The @Test
annotation says that the method is a test. In the next few sections, you'll update this code with a few tests.
Writing Your First Test
Replace the placeholder test with the code below:
package com.raywenderlich.nftmarketplace
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.servlet.MockMvc
@SpringBootTest
@AutoConfigureMockMvc
class NftMarketplaceApplicationTests(
@Autowired val mockMvc: MockMvc,
@Autowired val objectMapper: ObjectMapper
) {
}
To test the controller, you need MockMvc
and ObjectMapper
as the arguments of the test class's constructor. In addition, you annotate the class with @AutoConfigureMockMvc
and annotate the arguments with @Autowired
.
The MockMvc
sends requests to the controller while ObjectMapper
converts an object in Kotlin to a JSON string.
Create the first test case. Add the following method:
@Test
fun `Assert NFTs has CryptoPunks as the first item`() {
mockMvc.get("/nfts") // 1
.andExpect { // 2
status { isOk() } // 3
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$[0].id") { value(1) } // 4
jsonPath("$[0].name") { value("CryptoPunks") }
jsonPath("$[0].floor_price") { value(100) }
jsonPath("$.length()") { GreaterThan(1) }
}
}
Don't forget to add the relevant imports:
import org.junit.jupiter.api.Test
import org.mockito.internal.matchers.GreaterThan
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.get
Here's a code breakdown:
-
First, you use the
get
ofmockMvc
to send a GET request. -
Then you chain it with
andExpect
which accepts a function block. -
Inside the block, you test the status of the response with
status
and the content type withcontent
. -
Finally, you validate the JSON result with
jsonPath
which accepts an argument. As you can see, the$
represents the parsed object from the JSON string. To test the value of the argument ofjsonPath
, you need to wrap it withvalue
orGreaterThan
.
Time to run the test! Click the green play button on the left side of the method or class:
A pop-up dialog will appear. Confirm it:
You can see the test result on the run tool window:
Now, you'll add another test.
Writing Your Second Test
This time you'll test the creating NFT method. Add this test method below your existing test method:
@Test
fun `Assert that we can create an NFT`() {
mockMvc.get("/nfts/6")
.andExpect {
status { isNotFound() }
}
val newNFT = NFT(0, "Loot", 45.3)
mockMvc.post("/nfts") {
contentType = MediaType.APPLICATION_JSON
content = objectMapper.writeValueAsString(newNFT)
}
.andExpect {
status { isCreated() }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$.name") { value("Loot") }
jsonPath("$.floor_price") { value(45.3) }
jsonPath("$.id") { value(6) }
}
mockMvc.get("/nfts/6")
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
jsonPath("$.name") { value("Loot") }
jsonPath("$.floor_price") { value(45.3) }
jsonPath("$.id") { value(6) }
}
}
And import:
import com.raywenderlich.nftmarketplace.model.NFT
import org.springframework.test.web.servlet.post
Notice you use post
on mockMvc
with a function block on which you set the content type to JSON and the content to the JSON string. You use the writeValueAsString
of objectMapper
to convert a data class instance, newNFT
, to the JSON string.
Run the test, and you'll get a successful result.
Where To Go From Here?
You can download the final version of this project by clicking Download Materials at the top or bottom of this tutorial.
This application isn't complete. As a challenge, write a PUT request to update an existing NFT and a DELETE request to remove an NFT.
Be sure to check out the official documentation.
Do you want to write the traditional web application with HTML pages with Spring Boot? Head to the tutorial on building a blog on the Spring website. On the tutorial page, you'll also learn how to use a database in your Spring Boot project instead of the in-memory list that you used in this tutorial.
There's also a tutorial from kotlinlang.org that you should check out.
If you want to learn how to write a REST API using Ktor or compare it against Spring Boot, check out this tutorial.
I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!