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
- 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