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 & 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)) } }