Spring Mongo Rest WebFlux

How to build a Spring + Mongo + Rest application

1. Overview

This tutorial will provide an introduction to Spring Data JPA having a Spring project using Java 17, Lombok, Mongo, using a fully Reactive approach with Mongo and Spring WebFlux, it will cover configuring the persistence layer. For a step-by-step introduction to setting up the Spring context using Java-based configuration and you can check out the projection configuration and sources from our repository

2. MongoDB

MongoDB is a source-available cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with optional schemas. MongoDB is developed by MongoDB Inc

3. Document

Mongo is uses Documents in order to persist information into a Collection, and our model here is the POJO or the User class. We use the annotation @Document to set the collection name that will be used by the model. If the collection doesn't exist, MongoDB will create it.

                

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;


@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document
public class User {
    @Id
    private String id;
    private String fullName;
    private String email;
}                                       
                
            

User.java

                

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;


@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document
public class User {
    @Id
    private String id;
    private String fullName;
    private String email;
}

                    
                
            

User.kt

                

import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document

import scala.beans.BeanProperty

@Document
class User (@BeanProperty
            var fullName: String,
            @BeanProperty
            var email: String){
  @Id
  var id: String = _
  def this() = this(null,null)
}

                    
                
            

User.scala

4. Repository

Spring Data provides a repository layer that allows to avoid the boilerplate code of the Mongo queries

                
import com.the.sample.app.model.User;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;

@Repository
public interface UserRepository extends ReactiveCrudRepository {
    Mono findByEmail(String email);
}                                   
                
            

UserRepository.java

                

import com.the.sample.app.model.User;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;

@Repository
public interface UserRepository extends ReactiveCrudRepository {
    Mono findByEmail(String email);
}

                    
                
            

UserRepository.kt

                

import com.the.sample.app.model.User
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono

@Repository
trait UserRepository extends ReactiveCrudRepository[User,String]{
  def findByEmail(email: String): Mono[User]
}

                    
                
            

UserRepository.scala

5. Service

The service layer adds the business layer that we will need between our integration and the repository layers

                
import com.the.sample.app.model.User;
import com.the.sample.app.repository.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.UUID;

@Service
@AllArgsConstructor
public class UserServiceImpl implements UserService{
    private final UserRepository userRepository;

    @Override
    public Flux findAll() {
        return userRepository.findAll();
    }

    @Override
    public Mono findById(String id) {
        return userRepository.findById(id);
    }

    @Override
    public Mono findByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    @Override
    public void save(User user) {
        user.setId(UUID.randomUUID().toString());
        userRepository.save(user);
    }

    @Override
    public void deleteById(String id) {
        userRepository.deleteById(id);
    }
}                               
                
            

UserServiceImpl.java

                
import com.the.sample.app.model.User
import com.the.sample.app.repository.UserRepository
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.util.*

interface UserService {
    fun findAll(): Flux
    fun findById(id: String): Mono
    fun findByEmail(email: String): Mono
    fun save(user: User)
    fun deleteById(id: String)
}

@Service
class UserServiceImpl(val userRepository: UserRepository) : UserService{
    override fun findAll(): Flux {
        return userRepository.findAll()
    }
    override fun findById(id: String): Mono {
        return userRepository.findById(id)
    }

    override fun findByEmail(email: String): Mono {
        return userRepository.findByEmail(email)
    }

    override fun save(user: User) {
        user.id = UUID.randomUUID().toString()
        userRepository.save(user)
    }

    override fun deleteById(id: String) {
        userRepository.deleteById(id)
    }
}

                    
                
            

UserService.kt

                
import com.the.sample.app.model.User
import com.the.sample.app.repository.UserRepository
import org.springframework.stereotype.Service
import reactor.core.publisher.{Flux, Mono}

trait UserService {
  def findAll(): Flux[User]

  def findById(id: String): Mono[User]

  def findByEmail(email: String): Mono[User]

  def save(user: User): Unit

  def deleteById(id: String): Unit
}

@Service
class UserServiceImpl(userRepository: UserRepository) extends UserService {
  override def findAll(): Flux[User] =
    userRepository.findAll()

  override def findById(id: String): Mono[User] = userRepository.findById(id)

  override def findByEmail(email: String): Mono[User] = userRepository.findByEmail(email)

  override def save(user: User): Unit = userRepository.save(user)

  override def deleteById(id: String): Unit = userRepository.deleteById(id)
}

                    
                
            

UserService.scala

6. Rest Controller

This is our integration layer where our microservice can be called by Rest JSON using Spring WebFlux using the new Spring Reactive Stack

                
import com.the.sample.app.model.User;
import com.the.sample.app.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserRestController {
    private final UserService userService;

    @GetMapping("/")
    public Flux findAll() {
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public Mono findUserById(@PathVariable("id") String id) {
        return userService.findById(id);
    }


    @PostMapping("/")
    public Mono saverUser(@RequestBody User user) {
        userService.save(user);
        return Mono.just(user);
    }

    @PutMapping("/{id}")
    public Mono updateUser(@PathVariable("id") String id,@RequestBody User user) {
        user.setId(id);
        userService.save(user);
        return Mono.just(user);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable("id") String id) {
        userService.deleteById(id);
    }
}                               
                
            

UserRestController.java

                
import com.the.sample.app.model.User
import com.the.sample.app.service.UserService
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono

@RestController
@RequestMapping("/users")
class UserRestController(val userService: UserService) {

    @GetMapping("/{id}")
    fun getUserById(@PathVariable("id") id: String): Mono {
        return userService.findById(id)
    }

    @GetMapping("/")
    fun getAllUsers(): Flux {
        return userService.findAll()
    }

    @PostMapping("/")
    fun saverUser(@RequestBody user: User): Mono {
        userService.save(user)
        return Mono.just(user)
    }

    @PutMapping("/{id}")
    fun updateUser(@PathVariable("id") id: String, @RequestBody user: User): Mono? {
        user.id = id
        userService.save(user)
        return Mono.just(user)
    }

    @DeleteMapping("/{id}")
    fun deleteUser(@PathVariable("id") id: String): Unit {
        userService.deleteById(id)
    }
}

                    
                
            

UserRestController.kt

                
import com.the.sample.app.model.User
import com.the.sample.app.service.UserService
import org.springframework.web.bind.annotation.{
  DeleteMapping,
  GetMapping,
  PathVariable,
  PostMapping,
  PutMapping,
  RequestBody,
  RequestMapping,
  RestController
}
import reactor.core.publisher.{ Flux, Mono }

@RestController
@RequestMapping(Array("/users"))
class UserRestController(userService: UserService) {
  @GetMapping(Array("/{id}")) def getUserById(@PathVariable("id") id: String): Mono[User] =
    userService.findById(id)

  @GetMapping(Array("/")) def getAllUsers(): Flux[User] =
    userService.findAll()

  @PostMapping(Array("/")) def saverUser(@RequestBody user: User): Mono[User] = {
    userService.save(user)
    Mono.just(user)
  }

  @PutMapping(Array("/{id}")) def updateUser(@PathVariable("id") id: String, @RequestBody user: User): Mono[User] = {
    user.id = id
    userService.save(user)
    Mono.just(user)
  }

  @DeleteMapping(Array("/{id}")) def deleteUser(@PathVariable("id") id: String): Unit =
    userService.deleteById(id)
}

                    
                
            

UserRestController.scala

7. Spring Boot Context Configuration

This is the main component that starts our Spring Context and our Mongo Reactive Client

                

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

@SpringBootApplication
@EnableReactiveMongoRepositories
public class Application {

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

Application.java

                

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

@SpringBootApplication
@EnableReactiveMongoRepositories
public class Application {

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

                    
                
            

Application.kt

                
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories


@SpringBootApplication
@EnableReactiveMongoRepositories
class Application

object Application extends App{
  SpringApplication.run(classOf[Application])
}

                    
                
            

Application.scala

Database connection parameters are defined in the src/main/resources/application.properties
  • spring.data.mongodb.databas - Defines the Mongo database to connect
  • spring.data.mongodb.host - defines the Mongo host
  • spring.data.mongodb.username - Username to connect
  • spring.data.mongodb.password - Password to connect

8. Source Code

Checkout the whole source code for the following programming languages: