How to Create REST APIs with Spring Boot and JPA

Photo by Emile Mbunzama on Unsplash
Photo by Emile Mbunzama on Unsplash
This article will introduce how to use Spring Boot to create a back-end program, provide REST APIs, and connect to database.

In recent years, Spring Boot is a very popular back-end development framework. Spring Boot applications can be easily converted to micro-service application using Spring Cloud. This article will introduce how to use Spring Boot to create a back-end program, provide REST APIs, and connect to database.

The complete code for this chapter can be found in .

Creating a Project – Spring Initializr

The easiest way to create a Spring Boot project is to use the Spring Initializr online tool. You have to fill in these options to create a project.

  • Project: Choose Maven or Gradle build automation tool.
  • Language: Choose Java or Kotlin language.
  • Spring Boot: It’s better to choose latest stable version.
  • Project Metadata: Fill in group, artifact and name.
  • Packaging: Select packaging. If you choose Jar, Tomcat will be included and can be executed directly.
  • Java: Select the version of Java SDK. Kotlin is finally compiled into Java bytecode.
  • Dependencies: Click ADD DEPENDENCIES button to add packages. Let’s add the Spring Web framework, which allows the project to have REST APIs.
Spring Boot Initializr
Spring Boot Initializr

After selecting everything, click GENERATE at the bottom left to download the project, and then open it with IDE. For IDE, you can use Eclipse for free , and IntelliJ IDEA is recommended for paid one.

Creating RESTful Web Services

HTTP GET Requests

Let’s build a REST API. First, add User.

package com.waynestalk.demo.domain

data class User(val name: String?, var age: Int?)

Second, add UserController with 2 annotations:

  • @RestController: Combines @Controller and @ResponseBody. If a controller is to provide RESTful Web Service, it will use @RestController.
  • @RequestMapping: Map a Web Request to a path. In the project, it maps to /users. If it is added to a class, it means that all methods in the class will be under /users. If added to a method, it means only the method maps to /users.

In the UserController, add a GET Request handler.

  • @GetMapping : It maps an HTTP GET request to this method. We did not specify a sub-path here, so it will use /users specified to UserController. In addition, it is equivalent @RequestMapping(method = [RequestMethod.GET]).
  • @RequestParam : If a request URL is /users?age=20, it will parse the following query string. required means whether age is necessary to be set. default refers to use default value when it is not set.
  • @GetMapping (“/{name}”): All /users/XXX HTTP GET requests will maps to this method, XXX refers to any string.
  • @PathVariable : Put the XXX value into the name parameter.
package com.waynestalk.demo.controller

import com.waynestalk.demo.domain.User
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/users")
class UserController {
    private var users = listOf(
            User("Jason", 20),
            User("Alan", 22),
            User("David", 21),
            User("Monika", 20),
            User("Angela", 22)
    )

    @GetMapping
    fun getUsers(@RequestParam(required = false, defaultValue = "0") age: Int) =
            if (age == 0) users else users.filter { it.age == age }

    @GetMapping("/{name}")
    fun getUserBy(@PathVariable name: String) =
            users.find { it.name == name }
                    ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")
}

After executing the project, you can open http://localhost:8080/users directly with the browser to get all users:

[
   {
      "name":"Jason",
      "age":20
   },
   {
      "name":"Alan",
      "age":22
   },
   {
      "name":"David",
      "age":21
   },
   {
      "name":"Monika",
      "age":20
   },
   {
      "name":"Angela",
      "age":22
   }
]

You can also use curl command to send an HTTP GET request.

% curl --location --request GET 'http://localhost:8080/users'
[{"name":"Jason","age":20},{"name":"Alan","age":22},{"name":"David","age":21},{"name":"Monika","age":20},{"name":"Angela","age":22}]

Search for users whose age is 20.

% curl --location --request GET 'http://localhost:8080/users?age=20'
[{"name":"Jason","age":20},{"name":"Monika","age":20}]

Get User Monika.

% curl --location --request GET 'http://localhost:8080/users/Monika'
{"name":"Monika","age":20}

HTTP POST Requests

Next, we will add a REST API to add a User. In UserController, create a POST request handler.

  • @PostMapping : It is equivalent to@RequestMapping(method = [RequestMethod.POST])
  • @RequestBody : It will convert the body of the webrequest into a User object. It will choose a HttpMessageConverter to parse the body according to Content-Type in web request header
@RestController
@RequestMapping("/users")
class UserController {
    @PostMapping
    fun addUser(@RequestBody user: User): User {
        if (users.find { it.name == user.name } != null)
            throw ResponseStatusException(HttpStatus.CONFLICT, "Duplicated user ${user.name}")
        users.add(user)
        return user
    }
}

Use curl command to add User Sofia.

% curl --location --request POST 'http://localhost:8080/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "name": "Sofia",
    "age": 10
}'

HTTP PUT Requests

Generally speaking, POST requests are used to add something, while PUT request are used to modify something. Their usages are similar.

@RestController
@RequestMapping("/users")
class UserController {
    @PutMapping("/{name}")
    fun modifyUser(@PathVariable name: String, @RequestBody user: User): User {
        val found: User = users.find { it.name == name }
                ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")
        found.age = user.age
        return found
    }
}

Use curl to modify User Monika.

% curl --location --request PUT 'http://localhost:8080/users/Monika' \
--header 'Content-Type: application/json' \
--data-raw '{
    "age": 24
}'

HTTP DELETE Requests

DELETE Requests are used to delete something.

@RestController
@RequestMapping("/users")
class UserController {
    @DeleteMapping("/{name}")
    fun removeUser(@PathVariable name: String): User {
        val found = users.find { it.name == name }
                ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")
        users.remove(found)
        return found
    }
}

Use curl to delete User Monika.

% curl --location --request DELETE 'http://localhost:8080/users/Monika'

RESTful API Naming Conventions

We have introduced 4 types of HTTP requests above. But we only 2 URIs, one is /users and the other is /users/{name}. We might think why not every HTTP request has a unique URI? Like this:

  • GET: /users/get, /users/{name}/get
  • POST: /users/add
  • PUT: /users/{name}/update, or /users/update and specify name in the body
  • DELETE: /users/{name}/delete, or /users/delete and specify name in the body

The reason is explained in detail in the REST API Naming Convention . In short, REST APIs should be named according to resources instead of actions. In other words, resource /users/Monika provides 3 actions, allowing us to get data (GET), update (PUT), and remove (DELETE). And /users provides 2 actions so that we can get the list and add.

Connecting Database – JPA

JPA is short for Java Persistence API. It cannot be used directly, but instead defines a set of APIs that can be implemented by other frameworks. So with JPA, we can replace the underlying database without changing the program.

JPA uses Object Relational Mapping (ORM) architecture. ORM allows data table and data table fields map to their corresponding objects. Therefore, when using it, developers are facing with objects, not databases. So development is relatively easier and faster, and there is no need to write SQL directly.

H2

H2 is an in-memory database. In the early stages of a project, or when writing test programs, In-memory databases is always your choice.

To use H2, you need to include Spring Data JPA and H2 Database framework.

Spring Boot + H2
Spring Boot + H2

After adding those two dependencies, you will find the following lines added in build.gradle.kts:

// build.gradle.kts

plugins {
    kotlin("plugin.jpa") version "1.3.72"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("com.h2database:h2")
}

After including those frameworks, we need to modify our project to store a list of User in database.

First, modify User as follows. There are some annotations added.

  • @Entity : Set a class as an entity. class User will map to the user table.
  • @Column : When a class is set to an entity, each field in it will map to a column of the data table.  You can configure a table column using @Column. For example, here we set the name must be unique – Column(unique = true).
  • @Id : Set a column to be primary key.
  • @GeneratedValue : Automatically generate the value of the column set with @Id.
package com.waynestalk.demo.domain

import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id

@Entity
data class User(
    @Column(unique = true) val name: String?,
    var age: Int?,
    @Id @GeneratedValue var id: Long? = null)

Add UserRepository to access the database. UserRepository is an interface, and inheritance JpaRepository (and it inherites Repository). Spring will scan all interfaces inheriting Repository, and automatically generate corresponding classJpaRepository already defines some methods, and Spring will automatically generate them.

In addition, JPA has a naming mechanism, and Spring will automatically generate the corresponding code based on the method named by that mechanism. As shown below, we have customized two search methods. The naming mechanism is explained in detail in Query Creation .

package com.waynestalk.demo.repository

import com.waynestalk.demo.domain.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

interface UserRepository : JpaRepository<User, Long> {
    fun findAllByAge(age: Int): List<User>

    fun findByName(name: String): User?
}

Finally, modify UserController to let it access databases.

package com.waynestalk.demo.controller

import com.waynestalk.demo.domain.User
import com.waynestalk.demo.repository.UserRepository
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException

@RestController
@RequestMapping("/users")
class UserController(private val userRepository: UserRepository) {
    @GetMapping
    fun getUsers(@RequestParam(required = false, defaultValue = "0") age: Int): List<User> =
            if (age == 0) userRepository.findAll() else userRepository.findAllByAge(age)

    @GetMapping("/{name}")
    fun getUserBy(@PathVariable name: String) =
            userRepository.findByName(name)
                    ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")

    @PostMapping
    fun addUser(@RequestBody user: User) = userRepository.save(user)

    @PutMapping("/{name}")
    fun modifyUser(@PathVariable name: String, @RequestBody user: User): User {
        val found = userRepository.findByName(name)
                ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")
        if (user.age != null) {
            found.age = user.age
        }
        userRepository.save(found)
        return found
    }

    @DeleteMapping("/{name}")
    fun removeUser(@PathVariable name: String): User {
        val user = userRepository.findByName(name)
                ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User $name not found")
        userRepository.delete(user)
        return user
    }
}

For testing purposes, we insert some User data to the database when the program starts.

package com.waynestalk.demo

import com.waynestalk.demo.domain.User
import com.waynestalk.demo.repository.UserRepository
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class UserConfiguration {
    @Bean
    fun initUsers(userRepository: UserRepository) = ApplicationRunner {
        userRepository.saveAll(listOf(
                User("Jason", 20, 1),
                User("Alan", 22, 2),
                User("David", 21, 3),
                User("Monika", 20, 4),
                User("Angela", 22, 5)
        ))
    }
}

MySQL

MySQL is a very popular free database system. Many commercial applications use MySQL. Generally speaking, if companies are rich, use Oracle. Individuals and small companies use MySQL.

If you want to use MySQL in your applications, you need to include Spring Data JPA and MySQL Driver frameworks.

Spring Boot + MySQL
Spring Boot + MySQL

After including the frameworks, the following lines are added to build.gradle.kts.

// build.gradle.kts

plugins {
    kotlin("plugin.jpa") version "1.3.72"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("mysql:mysql-connector-java")
}

Finally, add some settings for data source in application.properties.

spring.datasource.url=jdbc:mysql://localhost:3306/dbname?useUnicode=true &characterEncoding=utf-8&autoReconnect=true&serverTimezone=Asia/Taipei
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.dialect.storage_engine=innodb

It’s finished to change database. Thanks to JPA, we don’t need to change any code!

Conclusion

If you are new to Spring Boot, you will definitely find Spring Boot difficult. This is because it has a lot of annotations to understand, but also because these annotations make the code of Spring Boot applications very small. For example, you don’t need to write code to set a method to map a HTTP request, but just declare it through an annotation. Spring Boot provides a lot of annotations to help us simplify this kind of code. After getting familiar with it, you will find it quite convenient!

Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like