Creating Simple RESTful Service in Kotlin and Spring Boot

Today you’ll see how to create a simple RESTful web service using Kotlin and Spring Boot. This article is adapted for those who are not familiar with Kotlin and Spring Boot before.

Prerequisites

  • IntelliJ IDEA
  • MySQL Server

First, we need to go to Spring Initializr website which allows us to build a template for the project:

 

Once we done, we get the project template with the following structure:

Now let’s add Maven dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
    <version>6.0.6</version>
</dependency>

 

The Source Code

Our service will have a Book entity that has the name and description properties. We will also describe Repository and Controller. The client code will be located in com.example.demo.service package, all other stuff we’ll put in com.example.demo.system

Book entity

Let’s create models.kt in com.example.demo.system namespace and add following code there:

package com.example.demo.system
import javax.persistence.*
import com.fasterxml.jackson.annotation.*

@Entity // Indicates that this class describes a data model
@Table (name = "books") // We state here how to name the table in the database
data class Book (
        @JsonProperty ("name") // We state how the property in the JSON object will be called
        @Column (name = "name", length = 200) // Setting field name in Database and its length
        val name: String = "", // Declare an immutable property name with an empty string as the default value

        @JsonProperty ("description")
        @Column (name = "description", length = 1000)
        val description: String = "",

        @Id // Inform the ORM that this field is a Primary Key
        @JsonProperty ("id")
        @Column (name = "id")
        @GeneratedValue (strategy = GenerationType.AUTO) // Autoincrement
        val id: Long = 0L
)

 

BookRepository

Create file repositories.kt in com.example.demo.system namespace with the following code:

package com.example.demo.system
import org.springframework.data.repository.*

interface BookRepository : CrudRepository<Book, Long> // Provides us with a set of CRUD operations

 

BookService

Create file BookService.kt in com.example.demo.service namespace with the following code:

package com.example.demo.service
import com.example.demo.system. *
import org.springframework.stereotype.Service

@Service // Let the IoC container implement the class
class BookService (private val bookRepository: BookRepository) {// Implements the repository as a dependency
    fun all (): Iterable <Book> = bookRepository.findAll () // Return the collection of entities

    fun get (id: Long): Book = bookRepository.findOne (id)

    fun add (book: Book): Book = bookRepository.save (book)
    // Save a copy of the object with the specified id in the database. 
    // Kotlin's idiom says that NOT changeable is always better than modifiable (no one will correct the value in another thread)
    // and offers a copy method for copying objects (special classes for data storage) with the ability to replace values
    fun edit (id: Long, book: Book): Book = bookRepository.save (book.copy (id = id)) 

    fun remove (id: Long) = bookRepository.delete (id)
}

BooksController

Create now controllers.kt file in com.example.demo.system namespace with the following code:

package com.example.demo.system
import com.example.demo.service. *
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation. *

@RestController // Describes how to handle http requests and how to send responses (serialization in JSON and vice versa)
@RequestMapping ("books") // Specify the route prefix for all actions
class BooksController (private val bookService: BookService) {// We implement our service as a dependency
    @GetMapping // We say that the action takes a GET request without parameters in the url
    fun index () = bookService.all () // And returns the result of the all method of our service

    @PostMapping // Action takes a POST request without parameters in the url
    @ResponseStatus (HttpStatus.CREATED) // Specify a specific HttpStatus on a successful response
    fun create (@RequestBody book: Book) = bookService.add (book) // Accept the Book object from the request body and pass it to the add method of our service

    @GetMapping ("{id}") // Here we say that this is a GET request with a parameter in the url (http: // localhost / books / {id})
    @ResponseStatus (HttpStatus.FOUND)
    fun read (@PathVariable id: Long) = bookService.get (id) // Inform that our id is of type Long and pass it to the service get method

    @PutMapping ("{id}")
    fun update (@PathVariable id: Long, @RequestBody book: Book) = bookService.edit (id, book) // Here we take one parameter from the url, the second from the request PUT body and return it to the edit

    @DeleteMapping ("{id}")
    fun delete (@PathVariable id: Long) = bookService.remove (id)
}

Setting up our service

Create demo scheme in the Database and configure application.properties in this way:

# -------------------------
# MySQL DB
# -------------------------

# Which driver will we use
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver

# User name to connect to the database
spring.datasource.username = ****

# Password to connect to the database
spring.datasource.password = ****

# Connection string indicating the database schema, time zone and the parameter that disables data encryption
spring.datasource.url = jdbc: mysql: //127.0.0.1: 3306 / demo? serverTimezone = UTC &amp; useSSL = false

# -------------------------
# ORM settings
# -------------------------

# Which dialect to use for generating tables
spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

# How to generate tables in a DB (to create, update, in any way ...)
spring.jpa.hibernate.ddl-auto = create

# Display in SQL queries
spring.jpa.show-sql = true

Testing the service

Create DemoApplicationTests class in com.example.demo namespace with the following code:

@SpringBootTest
@RunWith (SpringRunner :: class)
@FixMethodOrder (MethodSorters.NAME_ASCENDING) // Run the tests in alphabetical order
class DemoApplicationTests {
    private val baseUrl = "http: // localhost: 8080 / books /"
    private val jsonContentType = MediaType (MediaType.APPLICATION_JSON.type, MediaType.APPLICATION_JSON.subtype) // Write the http header to a variable for convenience
    private lateinit var mockMvc: MockMvc // Declare a variable with deferred initialization in which we will store the mock object

    @Autowired
    private lateinit var webAppContext: WebApplicationContext // Declare a variable with deferred initialization to which the application context will be embedded

    @Before // This method will be run before each test
    fun before () {
        mockMvc = webAppContextSetup (webAppContext) .build () // Create an object with the context of the
    }

    @Test
    fun `1 - Get empty list of books` () {
        val request = get (baseUrl) .contentType (jsonContentType) // Create a GET request at http: // localhost: 8080 / books / with http header Content-Type: application / json

        mockMvc.perform (request)
                .andExpect (status (). isOk) // Wait for http status 200 OK
                .andExpect (content (). json ("[]", true)) // wait for the empty JSON array in the response body
    }
    
    @Test
    fun `2 - Add first book` () {
        val passedJsonString = "" "
            {
                "name": "Java vs Kotlin",
                "description": "Switching to Kotlin from Java"
            }
        "" ".trimIndent ()

        val request = post (baseUrl) .contentType (jsonContentType) .content (passedJsonString)

        val resultJsonString = "" "
            {
                "name": "Java vs Kotlin",
                "description": "Switching to Kotlin from Java",
                "id": 1
            }
        "" ".trimIndent ()

        mockMvc.perform (request)
                .andExpect (status (). isCreated)
                .andExpect (content (). json (resultJsonString, true))
    }

    @Test
    fun `3 - Update first book` () {
        val passedJsonString = "" "
            {
                "name": "Java vs Kotlin",
                "description": "Switching to Kotlin from Java"
            }
        "" ".trimIndent ()

        val request = put (baseUrl + "1"). contentType (jsonContentType) .content (passedJsonString)

        val resultJsonString = "" "
            {
                "name": "Java vs Kotlin",
                "description": "Switching to Kotlin from Java",
                "id": 1
            }
        "" ".trimIndent ()

        mockMvc.perform (request)
                .andExpect (status (). isOk)
                .andExpect (content (). json (resultJsonString, true))
    }

    @Test
    fun `4 - Get first book` () {
        val request = get (baseUrl + "1"). contentType (jsonContentType)

        val resultJsonString = "" "
            {
                "name": "Java vs Kotlin",
                "description": "Switching to Kotlin from Java",
                "id": 1
            }
        "" ".trimIndent ()

        mockMvc.perform (request)
                .andExpect (status (). isFound)
                .andExpect (content (). json (resultJsonString, true))
    }
}

LEAVE A REPLY

Please enter your comment!
Please enter your name here

This site uses Akismet to reduce spam. Learn how your comment data is processed.