Building Reactive REST CRUD APIs Using Spring WebFlux & R2DBC

Hello everyone, Today we will learn how to use Spring Boot, Spring WebFlux, and R2DBC to create a reactive web service that interacts with H2 in-memory database.

Reactive APIs are non-blocking and tend to be more efficient because they’re not tying up processing while waiting for stuff to happen. Reactive systems adopt asynchronous I/O. Reactive apps allow us to scale better if we are dealing with lots of streaming data. 

If we are going to build a reactive app, we need it to be reactive all the way down to your database. Use a blocking JDBC driver with Spring WebFlux, and we will be displeased with its performance. Use a reactive NoSQL database like Cassandra, MongoDB, Couchbase, and Redis – and we will be satisfied with its performance.

Spring WebFlux uses a library called Reactor for its reactive support. The Reactor is an implementation of the Reactive Streams specification. The Reactor Provides two main types called Flux and Mono. Both of these types implement the Publisher interface provided by Reactive Streams. Flux is used to represent a stream of 0..N elements and Mono is used to represent a stream of 0..1 element.


Let’s build a Reactive Restful Service in Spring Boot

Now we are going to build a Restful API for a User CRUD application. We’ll build REST APIs for creating, retrieving, updating, and deleting user details. All the REST APIs will be asynchronous.


Project Structure:



Project Dependency(pom.xml)
<?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.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>springwebfluxunittestingexample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springwebfluxunittestingexample</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-h2</artifactId>
<scope>runtime</scope>
</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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>


Create a User table in Database(schema.sql)

Create schema.sql  file under resources folder.
CREATE TABLE USER (
ID INTEGER IDENTITY PRIMARY KEY ,
NAME VARCHAR(255) NOT NULL,
EMAIL VARCHAR (255) NOT NULL
);


Configure Datasource on application.yaml
logging:
level:
org.springframework.data.r2dbc: DEBUG
spring:
r2dbc:
url: r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
name: sa
password:


Define a Model Class 
package com.knf.dev.demo.springwebfluxunittestingexample.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

@Table("user")
@Getter
@Setter
@AllArgsConstructor
public class User {
@Id
private Long id;
private String name;
private String email;
}


Add a Reactive Crud Repository class [UserRepository.java]
package com.knf.dev.demo.springwebfluxunittestingexample.repository;

import com.knf.dev.demo.springwebfluxunittestingexample.model.User;
import org.springframework.data.repository.reactive
                                  .ReactiveCrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository
           extends ReactiveCrudRepository<User, Long> {
}


Implement a Rest Controller with Spring WebFlux
package com.knf.dev.demo.springwebfluxunittestingexample.controller;

import com.knf.dev.demo.springwebfluxunittestingexample.model.User;
import com.knf.dev.demo.springwebfluxunittestingexample.
                                       repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api")
public class UserController {

@Autowired
private UserRepository repository;

@GetMapping("/user")
Flux<User> getAll() {
return repository.findAll();
}

@GetMapping("/user/{id}")
Mono<User> getUser(@PathVariable("id") Long id) {
return repository.findById(id);
}

@PostMapping("/user")
Mono<User> addUser(@RequestBody User user) {
return repository.save(user);
}


@PutMapping("/user/{id}")
private Mono<User> updateUser(@PathVariable("id") Long id,
@RequestBody User user) {
return repository.findById(id).flatMap(user1 -> {
user.setId(id);
return repository.save(user);
}).switchIfEmpty(Mono.empty());
}

@DeleteMapping("/user/{id}")
Mono<Void> deleteById(@PathVariable("id") Long id) {
return repository.findById(id).flatMap(p ->
repository.deleteById(p.getId()));
}
}


Driver Class(Main class)
package com.knf.dev.demo.springwebfluxunittestingexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringwebfluxunittestingexampleApplication {

public static void main(String[] args) {
SpringApplication.
        run(SpringwebfluxunittestingexampleApplication.class, args);
}
}


Local Setup:

Step 1: Download or clone the source code to a local machine.

Step 2mvn clean install

Step 3: Run the Spring Boot application
mvn spring-boot:run


Test the endpoints using postman

1. Create user 


2. Update user



3. Fetch all user


4. Delete user



Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete