Here's a production-grade full project code snippet using Spring Boot WebFlux with functional-style routing. This includes:
- Clean folder structure
- DTOs, validation
- Global error handling
- Reactive service layer
- Functional handlers & routers
- MongoDB as persistence layer
π Project Structure (Maven)
src/
βββ main/
βββ java/com/example/userapi/
βββ config/
β βββ RouterConfig.java
βββ controller/
β βββ UserHandler.java
βββ dto/
β βββ UserDto.java
βββ exception/
β βββ GlobalExceptionHandler.java
β βββ UserNotFoundException.java
βββ model/
β βββ User.java
βββ repository/
β βββ UserRepository.java
βββ service/
β βββ UserService.java
βββ UserApiApplication.java
π¦ 1. pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
π§© 2. model/User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Document(collection = "users")
public class User {
@Id
private String id;
private String name;
private String email;
private int age;
}
π₯ 3. dto/UserDto.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto {
@NotBlank(message = "Name cannot be blank")
private String name;
@Email(message = "Email must be valid")
private String email;
@Min(value = 1, message = "Age must be at least 1")
private int age;
}
π 4. repository/UserRepository.java
public interface UserRepository extends ReactiveMongoRepository<User, String> {
Flux<User> findByNameContainingIgnoreCase(String name);
}
β 5. service/UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public Mono<User> createUser(UserDto dto) {
User user = User.builder()
.name(dto.getName())
.email(dto.getEmail())
.age(dto.getAge())
.build();
return userRepository.save(user);
}
public Mono<User> getUser(String id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)));
}
public Flux<User> getAllUsers() {
return userRepository.findAll();
}
public Flux<User> searchUsers(String name) {
return userRepository.findByNameContainingIgnoreCase(name);
}
public Mono<User> updateUser(String id, UserDto dto) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.flatMap(existing -> {
existing.setName(dto.getName());
existing.setEmail(dto.getEmail());
existing.setAge(dto.getAge());
return userRepository.save(existing);
});
}
public Mono<Void> deleteUser(String id) {
return userRepository.findById(id)
.switchIfEmpty(Mono.error(new UserNotFoundException(id)))
.flatMap(userRepository::delete);
}
}
π§ 6. handler/UserHandler.java
@Component
@RequiredArgsConstructor
public class UserHandler {
private final UserService userService;
private final Validator validator;
public Mono<ServerResponse> createUser(ServerRequest request) {
return request.bodyToMono(UserDto.class)
.flatMap(dto -> validate(dto))
.flatMap(userService::createUser)
.flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
}
public Mono<ServerResponse> getUser(ServerRequest request) {
String id = request.pathVariable("id");
return userService.getUser(id)
.flatMap(user -> ServerResponse.ok().bodyValue(user));
}
public Mono<ServerResponse> getAll(ServerRequest request) {
return ServerResponse.ok().body(userService.getAllUsers(), User.class);
}
public Mono<ServerResponse> search(ServerRequest request) {
String name = request.queryParam("name").orElse("");
return ServerResponse.ok().body(userService.searchUsers(name), User.class);
}
public Mono<ServerResponse> update(ServerRequest request) {
String id = request.pathVariable("id");
return request.bodyToMono(UserDto.class)
.flatMap(dto -> validate(dto))
.flatMap(dto -> userService.updateUser(id, dto))
.flatMap(user -> ServerResponse.ok().bodyValue(user));
}
public Mono<ServerResponse> delete(ServerRequest request) {
String id = request.pathVariable("id");
return userService.deleteUser(id)
.then(ServerResponse.noContent().build());
}
private Mono<UserDto> validate(UserDto dto) {
Errors errors = new BeanPropertyBindingResult(dto, UserDto.class.getName());
validator.validate(dto, errors);
if (errors.hasErrors()) {
return Mono.error(new ConstraintViolationException(errors.toString(), Set.of()));
}
return Mono.just(dto);
}
}
π 7. config/RouterConfig.java
@Configuration
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> route(UserHandler handler) {
return RouterFunctions.route()
.GET("/users", handler::getAll)
.GET("/users/{id}", handler::getUser)
.GET("/users/search", handler::search)
.POST("/users", handler::createUser)
.PUT("/users/{id}", handler::update)
.DELETE("/users/{id}", handler::delete)
.build();
}
}
β οΈ 8. exception/UserNotFoundException.java
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String id) {
super("User not found with ID: " + id);
}
}
π‘οΈ 9. exception/GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public Mono<ResponseEntity<String>> handleNotFound(UserNotFoundException ex) {
return Mono.just(ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage()));
}
@ExceptionHandler(Exception.class)
public Mono<ResponseEntity<String>> handleAll(Exception ex) {
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal error: " + ex.getMessage()));
}
}
π 10. UserApiApplication.java
@SpringBootApplication
public class UserApiApplication {
public static void main(String[] args) {
SpringApplication.run(UserApiApplication.class, args);
}
}
π οΈ 11. application.yml
spring:
data:
mongodb:
uri: mongodb://localhost:27017/userdb
main:
web-application-type: reactive
π§ͺ Test Example with curl
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name": "Dev", "email": "dev@example.com", "age": 28}'
curl http://localhost:8080/users
curl http://localhost:8080/users/123
Top comments (0)