This guide aims to help you create from scratch a CRUD RESTful API with Spring Boot. We'll create a User entity and develop its endpoints accordingly with a Rest Controller and a Service class.
We'll cover the following topics in this tutorial :
Tools used to make this tutorial :
We’ll first need to bootstrap our project with Spring Initializr.
This can be done by going to http://start.spring.io/ or if you’re using IntelliJ, by creating a New Project and choosing Spring Initializr instead of the standard Java option in the quick menu.
Be sure to select our chosen dependencies to work with, so be sure to include :
This step can also be done by writing the dependencies in the pom.xml file present in our project folder :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.users-api</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Users API</name>
<description>CodeFiction Spring API Project</description>
<properties>
<java.version>1.8</java.version>
</properties>
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-rest-hal-browser</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The first step coding-wise, is to create our User class.
src/main/java/com/usersapi/domain/user/User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
One thing that may come up to you as unclear, is why are we implementing serialization in our User class?
It’s due to the fact that serialization converts an object into a sequence of bytes which can then be saved to a database or transferred over a network. When working with our User entity, this may come in handy later on.
Notes about the annotations :
When working with JPA as our persistence layer, it’s necessary to map our API to persist our objects of choice.
As for the rest, this is when Lombok starts to show up in our application.
Next step is creating our UserRepository interface which will extend JpaRepository to access the methods necessary to manipulate the User table.
We’ll also need to pass the class that represents our model and the type of the primary key as generic arguments.
src/main/java/com/usersapi/domain/user/UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}
The @Repository annotation is from Spring and its purpose is simply to indicate that the class provides the mechanism for storage, retrieval, search, update and delete operation on objects.
In our src/main/resources/application.properties file, we’ll need to specify the data source for our Spring application.
spring.datasource.url=jdbc:h2:mem:usersapi
Some notes on what we are doing :
To create mock data for our User class, we’ll need to create a data.sql file and input our data there.
src/main/resources/data.sql
INSERT INTO user(name) VALUES('Neo');
INSERT INTO user(name) VALUES('Owt');
INSERT INTO user(name) VALUES('Three');
COMMIT;
For each endpoint, there will be a Rest Controller and a Service class to implement its logic. Our endpoints will be :
Creating a new user with the POST request
Since our Service class is the one that will implement the logic of our controller, we might as well start with it :
src/main/java/com/usersapi/endpoints/create/CreateUserService.java
@Service
public class CreateUserService {
@Autowired
UserRepository repository;
public User createNewUser(User user) {
return repository.save(user);
}
}
As for our controller :
src/main/java/com/usersapi/endpoints/create/CreateUserController.java
@RestController
@RequestMapping("/users")
public class CreateUserController {
@Autowired
CreateUserService service;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<User> createNewUser_whenPostUser(@RequestBody User user) {
User createdUser = service.createNewUser(user);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(createdUser.getId())
.toUri();
return ResponseEntity.created(uri).body(createdUser);
}
}
Building the structure of our GET requests : Detail and List
When retrieving the details of one specific user, we’ll need to fetch him by its id and manage the error possibility caused by a scenario that he doesn’t exist.
src/main/java/com/usersapi/endpoints/detail/DetailUserService.java
public class DetailUserService {
@Autowired
UserRepository repository;
public Optional<User> listUser(Long id) {
Optional<User> user = repository.findById(id);
if (!user.isPresent()) {
throw new UserNotFoundException(id);
} else {
return repository.findById(id);
}
}
}
No new annotations here. Our UserNotFoundException method will come from the following :
src/main/java/com/usersapi/endpoints/detail/UserNotFoundException.java
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("Could not find user with id " + id + ".");
}
}
When an UserNotFoundException is thrown, this extra tidbit of Spring MVC configuration will be used to render an HTTP 404 response.
src/main/java/com/usersapi/endpoints/detail/UserNotFoundAdvice.java
@ControllerAdvice
public class UserNotFoundAdvice {
@ResponseBody
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
String userNotFoundHandler(UserNotFoundException ex) {
return ex.getMessage();
}
}
As for our Controller, if the contact is found, we’ll return it with a HTTP 200 response.
src/main/java/com/usersapi/endpoints/detail/DetailUserController.java
@RestController
@RequestMapping("/users/{id}")
public class DetailUserController {
@Autowired
DetailUserService service;
@GetMapping
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<Optional<User>> listUser_whenGetUser(@PathVariable Long id) {
return ResponseEntity.ok().body(service.listUser(id));
}
}
When listing or retrieving all users, no exceptions will need to be handled, so this can be done by simply calling the repository.
src/main/java/com/usersapi/endpoints/list/ListUserService.java
@Service
public class ListUserService {
@Autowired
UserRepository repository;
public List<User> listAllUsers() {
return repository.findAll();
}
}
src/main/java/com/usersapi/endpoints/list/ListUserController.java
@RestController
@RequestMapping("/users")
public class ListUserController {
@Autowired
ListUserService service;
@GetMapping
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<List<User>> listAllUsers_whenGetUsers() {
return ResponseEntity.ok().body(service.listAllUsers());
}
}
Updating an existing user with the PUT request
When updating an user by its id, we need to check if it exists the same way we did with the listUser method.
Next, we’ll find the user that needs to be updated and save it with its new parameters. Our Service class should be something like :
src/main/java/com/usersapi/endpoints/update/UpdateUserService.java
@Service
public class UpdateUserService {
@Autowired
UserRepository repository;
public User updateUser(Long id, User user) {
Optional<User> userOptional = repository.findById(id);
if (!userOptional.isPresent()) {
throw new UserNotFoundException(id);
} else {
repository.findById(id);
return repository.save(user);
}
}
}
As for building our Controller it should be pretty straightforward by now, since our Service contains the logic behind our endpoint implementation.
All we need to do is pass our soon to be changed user and its id as parameters, also the @PutMapping annotation to handle the HTTP request.
src/main/java/com/usersapi/endpoints/update/UpdateUserController.java
@RestController
@RequestMapping("/users/{id}")
public class UpdateUserController {
@Autowired
UpdateUserService service;
@PutMapping
@ResponseStatus(HttpStatus.OK)
public ResponseEntity<User> updateUser_whenPutUser(@RequestBody User user, @PathVariable Long id) {
return ResponseEntity.ok().body(service.updateUser(id, user));
}
}
Removing an user from the database with the DELETE request
Removing an existing user with the delete request method is probably the simplest one. All we have to do is call the repository to delete an user by its id in case it’s found.
In case it doesn’t exist, we’ll need to throw our UserNotFoundException method.
src/main/java/com/usersapi/endpoints/delete/DeleteUserService.java
@Service
public class DeleteUserService {
@Autowired
UserRepository repository;
public void deleteUser(Long id) {
Optional<User> userOptional = repository.findById(id);
if (!userOptional.isPresent()) {
throw new UserNotFoundException(id);
} else {
repository.deleteById(id);
}
}
}
src/main/java/com/usersapi/endpoints/delete/DeleteUserController.java
@RestController
@RequestMapping("/users/{id}")
public class DeleteUserController {
@Autowired
DeleteUserService service;
@DeleteMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteUser_whenDeleteUser(@PathVariable Long id) {
service.deleteUser(id);
}
}
Throughout this tutorial, you have engaged in various steps to build a RESTful API/Web Service with CRUD operations, check out our website for more tutorials like this, our next steps with this API will be :
Available on our github page.