Spring Boot, MongoDB REST API using Kotlin.

As part of this article our focus to develop simple REST API using spring boot and MongoDB. 

Getting started with this is the Spring Initialiser tool: https://start.spring.io/

In this example, I am considering gradle as build tool and MongoDB as Database.

Download and import Project into your favorite editor, I prefer intellij,

Either you can install MongoDB on your local or you can use MongoDB hosted solution https://mlab.com/.

I am using mlab.com for this example.

Let us provide the MongoDB connection details in the application.properties

spring.data.mongodb.host=localhost #for now I kept localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=mongo-rest-api-kotlin-demo

Let us create entity class as Patient.

@Document
data class Patient (
        @Id
        val id: ObjectId = ObjectId.get(),
        val name: String,
        val description: String,
        val createdDate: LocalDateTime = LocalDateTime.now(),
        val modifiedDate: LocalDateTime = LocalDateTime.now()
)

@Document annotation rather than @Entity is used here for marking a class which objects we’d like to persist to the mongodb. 

@Id: is used for marking a field used for identification purposes. 

Also, we have provided some default values for the created date and modified date.

Let us create a repository interface.

import org.bson.types.ObjectId
import org.springframework.data.mongodb.repository.MongoRepository

interface PatientRepository : MongoRepository<Patient, String> {
    fun findOneById(id: ObjectId): Patient
    override fun deleteAll()

}

The repository interface is ready to use, we don’t have to write an implementation for it. This feature is provided by SpringData JPA. Also, MongoRepository interface provides all basic methods for CRUD operations. For now, we will consider only finOneById.

Now our backend is ready, let us write down the REST Controller which will serve our request efficiently.

@RestController
@RequestMapping("/patients")
class PatientController(
        private val patientsRepository: PatientRepository
) {

    @GetMapping
    fun getAllPatients(): ResponseEntity<List<Patient>> {
        val patients = patientsRepository.findAll()
        return ResponseEntity.ok(patients)
    }

    @GetMapping("/{id}")
    fun getOnePatient(@PathVariable("id") id: String): ResponseEntity<Patient> {
        val patient = patientsRepository.findOneById(ObjectId(id))
        return ResponseEntity.ok(patient)
    }
}

Now our basic Controller is ready, let us write some test cases.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith(SpringExtension::class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PatientControllerIntTest @Autowired constructor(
        private val patientRepository: PatientRepository,
        private val restTemplate: TestRestTemplate
) {
    private val defaultPatientId = ObjectId.get()

    @LocalServerPort
    protected var port: Int = 0

    @BeforeEach
    fun setUp() {
        patientRepository.deleteAll()
    }


    private fun getRootUrl(): String? = "http://localhost:$port/patients"

    private fun saveOnePatient() = patientRepository.save(Patient(defaultPatientId, "Name", "Description"))

    @Test
    fun `should return all patients`() {
        saveOnePatient()

        val response = restTemplate.getForEntity(
                getRootUrl(),
                List::class.java
        )

        assertEquals(200, response.statusCode.value())
        assertNotNull(response.body)
        assertEquals(1, response.body?.size)
    }

    @Test
    fun `should return single patient by id`() {
        saveOnePatient()

        val response = restTemplate.getForEntity(
                getRootUrl() + "/$defaultPatientId",
                Patient::class.java
        )

        assertEquals(200, response.statusCode.value())
        assertNotNull(response.body)
        assertEquals(defaultPatientId, response.body?.id)
    }
}

Here we are using spring boot test to do integration testing also SpringBootTest.WebEnvironment.RANDOM_PORT is used here.

Note:

https://kotlinlang.org/docs/reference/coding-conventions.html#naming-rules

Please consider the naming convention while writing test cases for kotlin.

In JVM world similar conventions are well-known in Groovy and Scalaworld.

Always start with simple steps first, we will write down get operation first, try to fetch all Patient details.

Run the application and hit the http://localhost:8090/patients endpoint.

Let us create a POST request.

Create one simple request Object that will help us to create entity in mango world.

class PatientRequest(
        val name: String,
        val description: String
)

Here we will Pass patient names and descriptions about treatment. 

Now go to the REST Controller and handle a POST request.

@PostMapping
fun createPatient(@RequestBody request: PatientRequest): ResponseEntity<Patient> {
    val patient = patientsRepository.save(Patient(
            name = request.name,
            description = request.description
    ))
    return ResponseEntity(patient, HttpStatus.CREATED)
}

Let us create a method to create a PUT method to handle amendments in a document.

@PutMapping("/{id}")
fun updatePatient(@RequestBody request: PatientRequest, @PathVariable("id") id: String): ResponseEntity<Patient> {
    val patient = patientsRepository.findOneById(ObjectId(id))
    val updatedPatient = patientsRepository.save(Patient(
            id = patient.id,
            name = request.name,
            description = request.description,
            createdDate = patient.createdDate,
            modifiedDate = LocalDateTime.now()
    ))
    return ResponseEntity.ok(updatedPatient)
}

Test Method for an Update operation.

@Test
fun `should update existing patient`() {
    saveOnePatient()
    val patientRequest = preparePatientRequest()

    val updateResponse = restTemplate.exchange(
            getRootUrl() + "/$defaultPatientId",
            HttpMethod.PUT,
            HttpEntity(patientRequest, HttpHeaders()),
            Patient::class.java
    )
    val patientRequest = patientRepository.findOneById(defaultPatientId)

    assertEquals(200, updateResponse.statusCode.value())
    assertEquals(defaultPatientId, patientRequest.id)
    assertEquals(patientRequest.description, patientRequest.description)
    assertEquals(patientRequest.name, patientRequest.name)
}

Now our update operation is ready.

Let us Delete records using Delete operation.

As the deleted document won’t be included in the response, the 204 code will be returned.

@DeleteMapping("/{id}")
fun deletePatient(@PathVariable("id") id: String): ResponseEntity<Unit> {
    patientsRepository.deleteById(id)
    return ResponseEntity.noContent().build()
}

Test method which is straight forward to test delete method.

@Test
fun `should delete existing patient`() {
    saveOnePatient()

    val delete = restTemplate.exchange(
            getRootUrl() + "/$defaultPatientId",
            HttpMethod.DELETE,
            HttpEntity(null, HttpHeaders()),
            ResponseEntity::class.java
    )

    assertEquals(204, delete.statusCode.value())
    assertThrows(EmptyResultDataAccessException::class.java) { patientRepository.findOneById(defaultPatientId) }
}

Now our all CRUD operations are ready, run the application

This is for now Code is available on Github

https://github.com/maheshwarLigade/springboot-mongodb.restapi/tree/master

Leave a Reply