Building Reactive REST CRUD APIs with Spring Boot, Spring WebFlux, Azure Cosmos DB and Azure Cosmos DB API for MongoDB
Hello everyone, Hope you are doing well. In this tutorial, you will learn how to build reactive REST CRUD APIs with Spring Boot, Spring WebFlux, Spring Data Reactive MongoDB, Azure Cosmos DB, and Azure Cosmos DB API for MongoDB.
A little bit of Background
Azure Cosmos DB
Azure Cosmos DB is a fully managed NoSQL database for modern app development. Single-digit millisecond response times, and automatic and instant scalability, guarantee speed at any scale.
Azure Cosmos DB API for MongoDB
The Azure Cosmos DB API for MongoDB makes it easy to use Cosmos DB as if it were a MongoDB database. You can apply your MongoDB experience and continue to use your favourite MongoDB drivers, SDKs, and tools by pointing your application to the API for the MongoDB account's connection string.
Spring Boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run".
More Info - https://spring.io/projects/spring-boot
Spring WebFlux
The original web framework included in the Spring Framework, Spring Web MVC, was purpose-built for the Servlet API and Servlet containers. The reactive-stack web framework, Spring WebFlux, was added later in version 5.0. It is fully non-blocking, supports Reactive Streams back pressure, and runs on such servers as Netty, Undertow, and Servlet 3.1+ containers.
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.
After completing this tutorial what we will build?
We will build REST APIs CRUD features:
Step 1: Sign in to Azure Portal and create a resource group
Sign in to Azure portal https://portal.azure.com/#home and find "Resource groups" like below.
Then, create a resource group like the one below.
Step 2: Create an Azure Cosmos DB
Sign in to Azure portal https://portal.azure.com/#home and search for "Azure Cosmos DB" like below.
You will be taken to a page like the below image, Then click on the "Create Azure Cosmos DB account" button.Then click on the "create" button of the "Azure Cosmos DB API for MongoDB" division.
You will be taken to a page like the below image,
You will be taken to a page like the below image, Then click on the "Create" button.
Now, You can see "Deployment is in progress" like the below image.
Once deployment is completed you can see the "Your deployment is complete" page like the below image.
Then go to the "Data Explorer" and click on the "New Collection".
You will be taken to a page like the below image, Then click on the "Create Azure Cosmos DB account" button.
Then click on the "create" button of the "Azure Cosmos DB API for MongoDB" division.You will be taken to a page like the below image,
Then click on the "Create" button.
Now, You can see "Deployment is in progress" like the below image.
Then go to the "Data Explorer" and click on the "New Collection".
Step 3: Creating a simple spring boot web application
First, open the Spring initializr https://start.spring.io/
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-webflux-cosmosdb-mongo-api-crud. Here I selected the Maven project - language Java - Spring Boot 2.7.1 and add Spring Reactive Web dependency and Spring Data Reactive MongoDB dependency.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-webflux-cosmosdb-mongo-api-crud.zip) file and downloads the project. Then, Extract the Zip file and import the project on your favourite IDE.
Final Project Directory
First, open the Spring initializr https://start.spring.io/
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-webflux-cosmosdb-mongo-api-crud. Here I selected the Maven project - language Java - Spring Boot 2.7.1 and add Spring Reactive Web dependency and Spring Data Reactive MongoDB dependency.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-webflux-cosmosdb-mongo-api-crud.zip) file and downloads the project.
Then, Extract the Zip file and import the project on your favourite IDE.
Final Project Directory
<?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.7.1</version>
<relativePath/>
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-webflux-cosmosdb-mongo-api-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-webflux-cosmosdb-mongo-api-crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<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.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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
<?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.7.1</version>
<relativePath/>
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-webflux-cosmosdb-mongo-api-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-webflux-cosmosdb-mongo-api-crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<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.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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
Configure your Spring Boot application to use your Azure Cosmos DB API for MongoDB.
spring.data.mongodb.database=<Database name>
spring.data.mongodb.uri=<PRIMARY CONNECTION STRING>
spring.data.mongodb.database=<Database name>
spring.data.mongodb.uri=<PRIMARY CONNECTION STRING>
Create User model
@Document(collection = "user")
public class User {
@Id
private String id;
private String name;
private String emailId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public User(String id, String name,
String emailId) {
this.id = id;
this.name = name;
this.emailId = emailId;
}
public User() {
}
}
- @Id annotation is currently used by Spring to support mapping for other non-relational persistence databases or frameworks that do not have a defined common persistence API like JPA.
- @Document is an annotation provided by the Spring Data project. It is used to identify a domain object, which is persisted to MongoDB. So you can use it to map a Java class into a collection inside MongoDB.
@Document(collection = "user")
public class User {
@Id
private String id;
private String name;
private String emailId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public User(String id, String name,
String emailId) {
this.id = id;
this.name = name;
this.emailId = emailId;
}
public User() {
}
}
- @Id annotation is currently used by Spring to support mapping for other non-relational persistence databases or frameworks that do not have a defined common persistence API like JPA.
- @Document is an annotation provided by the Spring Data project. It is used to identify a domain object, which is persisted to MongoDB. So you can use it to map a Java class into a collection inside MongoDB.
Create User repository
public interface UserRepository
extends ReactiveMongoRepository<User, String> {
}
By extending from the ReactiveMongoRepository, we have a reactive MongoDB repository.
public interface UserRepository
extends ReactiveMongoRepository<User, String> {
}
By extending from the ReactiveMongoRepository, we have a reactive MongoDB repository.
Create User service
public interface UserService {
Mono<User> save(User user);
Mono<User> delete(String id);
Mono<User> update(String id, User user);
Flux<User> findAll();
Mono<User> findById(String id);
}
public interface UserService {
Mono<User> save(User user);
Mono<User> delete(String id);
Mono<User> update(String id, User user);
Flux<User> findAll();
Mono<User> findById(String id);
}
Create User service implementation
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public Mono<User> save(User user) {
return this.userRepository.save(user);
}
@Override
public Mono<User> delete(String id) {
return this.userRepository
.findById(id).flatMap(p ->
this.userRepository
.deleteById(p.getId())
.thenReturn(p));
}
@Override
public Mono<User> update(String id, User user) {
return this.userRepository.findById(id)
.flatMap(u -> {
u.setId(id);
u.setEmailId(user.getEmailId());
u.setName(user.getName());
return save(u);
}).switchIfEmpty(Mono.empty());
}
@Override
public Flux<User> findAll() {
return this.userRepository.findAll();
}
@Override
public Mono<User> findById(String id) {
return this.userRepository.findById(id);
}
}
The @Service annotation represents that our bean holds some business logic.
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public Mono<User> save(User user) {
return this.userRepository.save(user);
}
@Override
public Mono<User> delete(String id) {
return this.userRepository
.findById(id).flatMap(p ->
this.userRepository
.deleteById(p.getId())
.thenReturn(p));
}
@Override
public Mono<User> update(String id, User user) {
return this.userRepository.findById(id)
.flatMap(u -> {
u.setId(id);
u.setEmailId(user.getEmailId());
u.setName(user.getName());
return save(u);
}).switchIfEmpty(Mono.empty());
}
@Override
public Flux<User> findAll() {
return this.userRepository.findAll();
}
@Override
public Mono<User> findById(String id) {
return this.userRepository.findById(id);
}
}
The @Service annotation represents that our bean holds some business logic.
Implement a Reactive Rest Controller with Spring WebFlux
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
private Mono<User> save(@RequestBody User user) {
return this.userService.save(user);
}
@DeleteMapping("/users/{id}")
private Mono<ResponseEntity<String>> delete
(@PathVariable("id") String id) {
return this.userService.delete(id)
.flatMap(user -> Mono.just(ResponseEntity
.ok("Deleted Successfully")))
.switchIfEmpty(Mono.just(ResponseEntity
.notFound().build()));
}
@PutMapping("/users/{id}")
private Mono<ResponseEntity<User>> update
(@PathVariable("id") String id,
@RequestBody User user) {
return this.userService.update(id, user)
.flatMap(user1 -> Mono.just(ResponseEntity
.ok(user1))).switchIfEmpty(Mono
.just(ResponseEntity.notFound().build()));
}
@GetMapping(value = "/users")
private Flux<User> findAll() {
return this.userService.findAll();
}
@GetMapping("/users/{id}")
private Mono<User> findUserById
(@PathVariable("id") String id)
{
return this.userService.findById(id);
}
}
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/users")
@ResponseStatus(HttpStatus.CREATED)
private Mono<User> save(@RequestBody User user) {
return this.userService.save(user);
}
@DeleteMapping("/users/{id}")
private Mono<ResponseEntity<String>> delete
(@PathVariable("id") String id) {
return this.userService.delete(id)
.flatMap(user -> Mono.just(ResponseEntity
.ok("Deleted Successfully")))
.switchIfEmpty(Mono.just(ResponseEntity
.notFound().build()));
}
@PutMapping("/users/{id}")
private Mono<ResponseEntity<User>> update
(@PathVariable("id") String id,
@RequestBody User user) {
return this.userService.update(id, user)
.flatMap(user1 -> Mono.just(ResponseEntity
.ok(user1))).switchIfEmpty(Mono
.just(ResponseEntity.notFound().build()));
}
@GetMapping(value = "/users")
private Flux<User> findAll() {
return this.userService.findAll();
}
@GetMapping("/users/{id}")
private Mono<User> findUserById
(@PathVariable("id") String id)
{
return this.userService.findById(id);
}
}
Spring Boot Driver
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The Spring Boot application's main class contains a public static void main() method that starts up the Spring ApplicationContext.
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The Spring Boot application's main class contains a public static void main() method that starts up the Spring ApplicationContext.
Download the complete source code - click here
Step 4: Local Setup and Run the application
Step 1: Download or clone the source code from GitHub to the local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application -
mvn spring-boot:run
Step 1: Download or clone the source code from GitHub to the local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application -
mvn spring-boot:run