Minimal Webservice with data classes and spring boot data rest


Spring Boot Data REST and Kotlin data classes enable an very code efficient way to provide CRUD Interfaces.

We will create a small webservice for managing persons.

Setting up

For this tutorial we will use gradle with kotlin dsl to build the application.

mkdir spring_data_class
cd spring_data_class
gradle init --dsl kotlin 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
plugins {
    kotlin("jvm") version "1.3.0"
    id ("org.springframework.boot") version "2.1.1.RELEASE"
}

apply(plugin = "io.spring.dependency-management")

dependencies {
    compile(kotlin("stdlib"))

    // Necessary to enable spring to use reflection on kotlin classes
    compile("org.jetbrains.kotlin:kotlin-reflect:1.3.10")

    // Spring Boot Data JPA for Persisting and REST for web access
    compile("org.springframework.boot:spring-boot-starter-data-jpa")
    compile("org.springframework.boot:spring-boot-starter-data-rest")

    // To avoid having to setup a big sql database we use h2 here
    runtime("com.h2database:h2:1.4.197")
}

repositories {
    jcenter()
}

We should be able to execute it without any error:

gradle clean build

Bootstrapping the App

In Kotlin we dont need to define a main() method in the App class. We simply add a main function and a empty App-class for the @SpringBootApplication annotation.

We need to define it as open to allow spring to subclass it.

src/main/kotlin/main/App.kt

1
2
3
4
5
6
@SpringBootApplication
open class App

fun main(args: Array<String>) {
    SpringApplication(App::class.java).run(*args)
}

Imports are ommitted to keep the code examples short

To be able to start it we first need to set the database driver to H2 in the application properties:

src/main/resources/application.yml

1
2
3
spring:
  datasource:
    driver-class-name: "org.h2.Driver"

Now we can start it by calling the bootRun gradle task.

gradle bootRun

Defining the Person

To keep thins simple we will define our person to have an Long id and a String name.

src/main/kotlin/main/Person.kt

1
2
3
@Entity
data class Person(@Id @GeneratedValue var id: Long? = null,
                  @NotEmpty var name: String = "")

We allow id to be null by defining it as Long?. If we disabled nullability we wont be able to add a new Person because the id is assigned by JPA.

Adding the Repository

We will add a repository to be able to add and query Persons. This repository will also function as the REST access point.

src/main/kotlin/main/PersonRepository.kt

1
2
3
4
5
6
// Mount the resource at /person
@RepositoryRestResource(path = "person")
interface PersonRepository : JpaRepository<Person, Long> {
    // add an own method for searching by name
    fun findByNameContaining(@RequestParam("name") name: String): List<Person>
}

This is all we need to add and query persons through rest.

We start the spring app using gradle:

gradle bootRun

I will user curl to access the REST resource.

We will need a json file describing the person to add:

person.json

{
  "name":"Lukas"
}

Add person:

< curl http://localhost:8080/person -d @person.json -H "Content-Type: application/json"
> {
>   "name" : "Lukas",
>   "_links" : {
>     "self" : {
>       "href" : "http://localhost:8080/person/1"
>     },
>     "person" : {
>       "href" : "http://localhost:8080/person/1"
>     }
>   }
> }

If you execute this script on windows you might add an ``` before @ to escape the character

Query person:

< curl http://localhost:8080/person
> {
>   "_embedded" : {
>     "persons" : [ {
>       "name" : "Lukas",
>       "_links" : {
>         "self" : {
>           "href" : "http://localhost:8080/person/1"
>         },
>         "person" : {
>           "href" : "http://localhost:8080/person/1"
>         }
>       }
>     } ]
>   },
>   "_links" : {
>     "self" : {
>       "href" : "http://localhost:8080/person{?page,size,sort}",
>       "templated" : true
>     },
>     "profile" : {
>       "href" : "http://localhost:8080/profile/person"
>     },
>     "search" : {
>       "href" : "http://localhost:8080/person/search"
>     }
>   },
>   "page" : {
>     "size" : 20,
>     "totalElements" : 1,
>     "totalPages" : 1,
>     "number" : 0
>   }
> }

Find by name contains

< curl http://localhost:8080/person/search/findByNameContaining?name=Lu
> {
>   "_embedded" : {
>     "persons" : [ {
>       "name" : "Lukas",
>       "_links" : {
>         "self" : {
>           "href" : "http://localhost:8080/person/1"
>         },
>         "person" : {
>           "href" : "http://localhost:8080/person/1"
>         }
>       }
>     } ]
>   },
>   "_links" : {
>     "self" : {
>       "href" : "http://localhost:8080/person/search/findByNameContaining?name=Lu"
>     }
>   }
> }

You might wonder why there are so many _links items in the output. Those are links to other endpoints of the api to provide a sort of auto discovery mechanism.

Adding the id

You might also wonder why there is only the name in the output of the rest api. Thats because spring wont render the id by default. To force spring to render the id into the answer we must configure it through a RepositoryRestConfiguration.

src/main/kotlin/main/RestConfiguration.kt

1
2
3
4
5
6
7
8
9
@Configuration
// Open class to enale reflection in spring
open class RestConfiguration : RepositoryRestConfigurer {

    override fun configureRepositoryRestConfiguration(config: RepositoryRestConfiguration?) {
    // Expose id in answer
        config?.exposeIdsFor(Person::class.java)
    }
}

No we will see the id in the answer:

< curl http://localhost:8080/person/1
> {
>   "id" : 1,
>   "name" : "Lukas",
>   "_links" : {
>     "self" : {
>       "href" : "http://localhost:8080/person/1"
>     },
>     "person" : {
>       "href" : "http://localhost:8080/person/1"
>     }
>   }
> }

Wrapping up

We created a small web service for CRUD Operations on persons. The whole project is available on github: data_classes_spring.

For further information I would recommend the spring documentation on Data JPA and Data REST.