🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (178K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
In this tutorial, we will learn how to build a reactive CRUD REST API using Spring Boot, Spring WebFlux, MongoDB, Lombok, and IntelliJ IDEA.
CRUD stands for "create, read, update, and delete," which are the four basic functions of persistent storage. Spring WebFlux is a non-blocking, reactive web framework for building reactive, scalable web applications. Together, Spring WebFlux and CRUD can be used to quickly develop a reactive RESTful API that can create, read, update, and delete data in a database.
Spring WebFlux
Spring WebFlux is a non-blocking, reactive web framework for building reactive, scalable web applications. It is part of the Spring Framework, and it is built on top of Project Reactor, which is a reactive programming library for building asynchronous, non-blocking applications.Mono and Flux
The Mono API allows producing only one value.
Flux: Returns 0…N elements.
The Flux can be endless, it can produce multiple values.
Mono vs Flux
Programming models supported by Spring WebFlux
- The traditional annotation-based model with @Controller, @RestController, @RequestMapping, and other annotations that you have been using in Spring MVC.
- A brand new Functional style model based on Java 8 lambdas for routing and handling requests.
Reactive MongoDB Driver
1. Maven Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</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>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>2. Project Structure
Refer to the below screenshot to create the packing or project structure for the application:
3. Configure MongoDB
spring.data.mongodb.uri=mongodb://localhost:27017/ems4. Create Domain Class
package net.javaguides.springbootwebfluxdemo.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Document(value = "employees")
public class Employee {
@Id
private String id;
private String firstName;
private String lastName;
private String email;
}5. Creating Repository - EmployeeRepository
package net.javaguides.springbootwebfluxdemo.repository;
import net.javaguides.springbootwebfluxdemo.entity.Employee;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface EmployeeRepository extends ReactiveCrudRepository<Employee, String> {
}6. Create EmployeeDTO and EmployeeMapper - Map Entity to Dto and Vice Versa
EmployeeDto
package net.javaguides.springbootwebfluxdemo.dto;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class EmployeeDto {
private String id;
private String firstName;
private String lastName;
private String email;
}EmployeeMapper - Map Entity to Dto and Vice Versa
package net.javaguides.springbootwebfluxdemo.mapper;
import net.javaguides.springbootwebfluxdemo.dto.EmployeeDto;
import net.javaguides.springbootwebfluxdemo.entity.Employee;
public class EmployeeMapper {
public static EmployeeDto mapToEmployeeDto(Employee employee){
return new EmployeeDto(
employee.getId(),
employee.getFirstName(),
employee.getLastName(),
employee.getEmail()
);
}
public static Employee mapToEmployee(EmployeeDto employeeDto){
return new Employee(
employeeDto.getId(),
employeeDto.getFirstName(),
employeeDto.getLastName(),
employeeDto.getEmail()
);
}
}7. Create a Service Layer
EmployeeService Interface
package net.javaguides.springbootwebfluxdemo.service;
import net.javaguides.springbootwebfluxdemo.dto.EmployeeDto;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface EmployeeService {
Mono<EmployeeDto> saveEmployee(EmployeeDto employeeDto);
Mono<EmployeeDto> getEmployee(String employeeId);
Flux<EmployeeDto> getAllEmployees();
Mono<EmployeeDto> updateEmployee(EmployeeDto employeeDto, String employeeId);
Mono<Void> deleteEmployee(String employeeId);
}EmployeeServiceImpl class
package net.javaguides.springbootwebfluxdemo.service.impl;
import lombok.AllArgsConstructor;
import net.javaguides.springbootwebfluxdemo.dto.EmployeeDto;
import net.javaguides.springbootwebfluxdemo.entity.Employee;
import net.javaguides.springbootwebfluxdemo.mapper.EmployeeMapper;
import net.javaguides.springbootwebfluxdemo.repository.EmployeeRepository;
import net.javaguides.springbootwebfluxdemo.service.EmployeeService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
@AllArgsConstructor
public class EmployeeServiceImpl implements EmployeeService {
private EmployeeRepository employeeRepository;
@Override
public Mono<EmployeeDto> saveEmployee(EmployeeDto employeeDto) {
Employee employee = EmployeeMapper.mapToEmployee(employeeDto);
Mono<Employee> savedEmployee = employeeRepository.save(employee);
return savedEmployee
.map((employeeEntity) -> EmployeeMapper.mapToEmployeeDto(employeeEntity));
}
@Override
public Mono<EmployeeDto> getEmployee(String employeeId) {
Mono<Employee> employeeMono = employeeRepository.findById(employeeId);
return employeeMono.map((employee -> EmployeeMapper.mapToEmployeeDto(employee)));
}
@Override
public Flux<EmployeeDto> getAllEmployees() {
Flux<Employee> employeeFlux = employeeRepository.findAll();
return employeeFlux
.map((employee) -> EmployeeMapper.mapToEmployeeDto(employee))
.switchIfEmpty(Flux.empty());
}
@Override
public Mono<EmployeeDto> updateEmployee(EmployeeDto employeeDto, String employeeId) {
Mono<Employee> employeeMono = employeeRepository.findById(employeeId);
return employeeMono.flatMap((existingEmployee) -> {
existingEmployee.setFirstName(employeeDto.getFirstName());
existingEmployee.setLastName(employeeDto.getLastName());
existingEmployee.setEmail(employeeDto.getEmail());
return employeeRepository.save(existingEmployee);
}).map((employee -> EmployeeMapper.mapToEmployeeDto(employee)));
}
@Override
public Mono<Void> deleteEmployee(String employeeId) {
return employeeRepository.deleteById(employeeId);
}
}8. Create Controller Layer - Reactive REST APIs
package net.javaguides.springbootwebfluxdemo.controller;
import lombok.AllArgsConstructor;
import net.javaguides.springbootwebfluxdemo.dto.EmployeeDto;
import net.javaguides.springbootwebfluxdemo.service.EmployeeService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("api/employees")
@AllArgsConstructor
public class EmployeeController {
private EmployeeService employeeService;
@PostMapping
@ResponseStatus(value = HttpStatus.CREATED)
public Mono<EmployeeDto> saveEmployee(@RequestBody EmployeeDto employeeDto){
return employeeService.saveEmployee(employeeDto);
}
@GetMapping("{id}")
public Mono<EmployeeDto> getEmployee(@PathVariable("id") String employeeId){
return employeeService.getEmployee(employeeId);
}
@GetMapping
public Flux<EmployeeDto> getAllEmployees(){
return employeeService.getAllEmployees();
}
@PutMapping("{id}")
public Mono<EmployeeDto> updateEmployee(@RequestBody EmployeeDto employeeDto,
@PathVariable("id") String employeeId){
return employeeService.updateEmployee(employeeDto, employeeId);
}
@DeleteMapping("{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public Mono<Void> deleteEmployee(@PathVariable("id") String employeeId){
return employeeService.deleteEmployee(employeeId);
}
}9. Testing Reactive CRUD REST APIs using WebClientTest Class
package net.javaguides.springboot;
import net.javaguides.springboot.dto.EmployeeDto;
import net.javaguides.springboot.repository.EmployeeRepository;
import net.javaguides.springboot.service.EmployeeService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collections;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class EmployeeControllerIntegrationTests {
@Autowired
private EmployeeService employeeService;
@Autowired
private WebTestClient webTestClient;
@Autowired
private EmployeeRepository employeeRepository;
@BeforeEach
public void before(){
System.out.println("Before Each Test");
employeeRepository.deleteAll().subscribe();
}
@Test
public void testSaveEmployee(){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName("John");
employeeDto.setLastName("Cena");
employeeDto.setEmail("john@gmail.com");
webTestClient.post().uri("/api/employees")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(employeeDto), EmployeeDto.class)
.exchange()
.expectStatus().isCreated()
.expectBody()
.consumeWith(System.out::println)
.jsonPath("$.firstName").isEqualTo(employeeDto.getFirstName())
.jsonPath("$.lastName").isEqualTo(employeeDto.getLastName())
.jsonPath("$.email").isEqualTo(employeeDto.getEmail());
}
@Test
public void testGetSingleEmployee(){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName("Meena");
employeeDto.setLastName("Fadatare");
employeeDto.setEmail("meena@gmail.com");
EmployeeDto savedEmployee = employeeService.saveEmployee(employeeDto).block();
webTestClient.get().uri("/api/employees/{id}", Collections.singletonMap("id",savedEmployee.getId()))
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(System.out::println)
.jsonPath("$.id").isEqualTo(savedEmployee.getId())
.jsonPath("$.firstName").isEqualTo(employeeDto.getFirstName())
.jsonPath("$.lastName").isEqualTo(employeeDto.getLastName())
.jsonPath("$.email").isEqualTo(employeeDto.getEmail());
}
@Test
public void testGetAllEmployees(){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName("John");
employeeDto.setLastName("Cena");
employeeDto.setEmail("john@gmail.com");
employeeService.saveEmployee(employeeDto).block();
EmployeeDto employeeDto1 = new EmployeeDto();
employeeDto1.setFirstName("Meena");
employeeDto1.setLastName("Fadatare");
employeeDto1.setEmail("meena@gmail.com");
employeeService.saveEmployee(employeeDto1).block();
webTestClient.get().uri("/api/employees")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk()
.expectBodyList(EmployeeDto.class)
.consumeWith(System.out::println);
}
@Test
public void testUpdateEmployee(){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName("Ramesh");
employeeDto.setLastName("Fadatare");
employeeDto.setEmail("ramesh@gmail.com");
EmployeeDto savedEmployee = employeeService.saveEmployee(employeeDto).block();
EmployeeDto updatedEmployee = new EmployeeDto();
updatedEmployee.setFirstName("Ram");
updatedEmployee.setLastName("Jadhav");
updatedEmployee.setEmail("ram@gmail.com");
webTestClient.put().uri("/api/employees/{id}", Collections.singletonMap("id", savedEmployee.getId()))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(updatedEmployee), EmployeeDto.class)
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(System.out::println)
.jsonPath("$.firstName").isEqualTo(updatedEmployee.getFirstName())
.jsonPath("$.lastName").isEqualTo(updatedEmployee.getLastName())
.jsonPath("$.email").isEqualTo(updatedEmployee.getEmail());
}
@Test
public void testDeleteEmployee(){
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setFirstName("Ramesh");
employeeDto.setLastName("Fadatare");
employeeDto.setEmail("ramesh@gmail.com");
EmployeeDto savedEmployee = employeeService.saveEmployee(employeeDto).block();
webTestClient.delete().uri("/api/employees/{id}", Collections.singletonMap("id", savedEmployee.getId()))
.exchange()
.expectStatus().isNoContent()
.expectBody()
.consumeWith(System.out::println);
}
}Output:
Conclusion
My Top and Bestseller Udemy Courses. The sale is going on with a 70 - 80% discount. The discount coupon has been added to each course below:
Build REST APIs with Spring Boot 4, Spring Security 7, and JWT
[NEW] Learn Apache Maven with IntelliJ IDEA and Java 25
ChatGPT + Generative AI + Prompt Engineering for Beginners
Spring 7 and Spring Boot 4 for Beginners (Includes 8 Projects)
Available in Udemy for Business
Building Real-Time REST APIs with Spring Boot - Blog App
Available in Udemy for Business
Building Microservices with Spring Boot and Spring Cloud
Available in Udemy for Business
Java Full-Stack Developer Course with Spring Boot and React JS
Available in Udemy for Business
Build 5 Spring Boot Projects with Java: Line-by-Line Coding
Testing Spring Boot Application with JUnit and Mockito
Available in Udemy for Business
Spring Boot Thymeleaf Real-Time Web Application - Blog App
Available in Udemy for Business
Master Spring Data JPA with Hibernate
Available in Udemy for Business
Spring Boot + Apache Kafka Course - The Practical Guide
Available in Udemy for Business


Comments
Post a Comment
Leave Comment