Building a Robust Multi-Module Content Platform with Spring Boot and MongoDB
As online platforms grow, the need for robust content moderation becomes essential to ensure community guidelines and quality standards are met. Building a scalable and maintainable content moderation platform is no small feat, especially when aiming to support features like secure user authentication, content workflows, and high availability.
In this post, I’ll walk you through how I built a Content Platform using Spring Boot, MongoDB, Mongock, JWT Authentication, and a multi-Module architecture. Each module focuses on a specific feature to ensure modularity and scalability.
What Is the Content Moderation Platform?
This platform allows users to:
Let’s dive into how this was accomplished.
Project Structure
The platform is built using a multi-Module Maven project. Each module is dedicated to a specific functionality. Here’s an overview of the modules:
application: The entry point for the application.
authentication: Handles user authentication and JWT management.
app-service: Manages controllers.
blog: Manages blog content, including moderation workflows.
common-service: Houses shared utilities, exception handling, and constants.
utility-modules: Provides helper utilities and reusable logic.
Each module has its own pom.xml file, dependencies, and clearly defined responsibilities.
Application Module
The Application Module acts as the backbone of the project and is the main entry point. Here’s a more detailed explanation:
Purpose of the Application Module
Authentication Module
The Authentication module provides secure user registration and login functionality. It leverages Spring Security and JWT to authenticate and authorize users.
Security Configuration: The SecurityConfig class defines the security rules and integrates JWT-based authentication:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Value("${security.jwt.secret-key}")
private String jwtSecretKey;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/v1/blog/**").permitAll()
.requestMatchers("/api/v1/account/login", "/api/v1/account/register").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
@Bean
public JwtDecoder jwtDecoder() {
var secretKey = new SecretKeySpec(jwtSecretKey.getBytes(), "");
return NimbusJwtDecoder.withSecretKey(secretKey)
.macAlgorithm(MacAlgorithm.HS256).build();
}
@Bean
public AuthenticationManager authenticationManager(AppUserService appUserService) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(appUserService);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return new ProviderManager(provider);
}
}
package com.techbellys.utils;
import com.nimbusds.jose.jwk.source.ImmutableSecret;
import com.techbellys.entity.AppUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Component
public class JwtUtil {
@Value("${security.jwt.secret-key}")
private String jwtSecretKey;
@Value("${security.jwt.issuer}")
private String jwtIssuer;
public String createJwtToken(AppUser appUser) {
Instant now = Instant.now();
// Build JWT claims
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer(jwtIssuer) // Issuer of the token
.issuedAt(now) // Token issued at
.expiresAt(now.plusSeconds(24 * 3600)) // Token expiration (24 hours)
.subject(appUser.getId()) // Subject of the token (user ID)
.claim("username", appUser.getUsername()) // Username claim
.claim("email", appUser.getEmail()) // Email claim
.claim("full_name", appUser.getFull_name()) // Full name claim
.claim("role", appUser.getRole()) // Role claim
.build();
// Configure JWT encoder
NimbusJwtEncoder encoder = new NimbusJwtEncoder(
new ImmutableSecret<>(jwtSecretKey.getBytes()));
JwtEncoderParameters params = JwtEncoderParameters.from(
JwsHeader.with(MacAlgorithm.HS256).build(), claims);
// Encode and return the JWT token
return encoder.encode(params).getTokenValue();
}
}
package com.techbellys.utils;
import com.techbellys.dto.AppUserDto;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
@Component
public class UserUtil {
public AppUserDto extractUserFromAuth(Authentication authentication) {
// Extract user details from Authentication
if (authentication == null || !authentication.isAuthenticated()) {
throw new RuntimeException("Unauthorized access");
}
// Assuming the JWT holds user details in the principal or claims
var principal = authentication.getPrincipal();
// Handle Jwt principal
if (principal instanceof Jwt jwt) {
AppUserDto user = new AppUserDto();
user.setId(jwt.getClaimAsString("sub")); // Extract user ID (sub claim)
user.setUsername(jwt.getClaimAsString("username")); // Extract username
user.setFull_name(jwt.getClaimAsString("full_name"));
user.setRole(jwt.getClaimAsString("role"));
user.setEmail(jwt.getClaimAsString("email"));
return user;
}
throw new RuntimeException("Failed to extract user details from authentication");
}
}
Authentication Flow
Registration:
Login:
Profile Retrieval:
Common Service
common-module serves as a utility and reusable components library for your application. It contains various functionalities and abstractions that other modules (like your authentication module) can utilize.
The Common Module acts as a centralized repository for shared logic, utilities, constants, and custom exceptions. This modularity reduces redundancy, promotes reusability, and ensures consistent functionality across your application.
Common Use Cases for This Module
Centralized Exception Handling:
Shared Constants and Utilities:
Standardized API Responses:
Reusability in Entity Design:
Benefits of the Common Module
Blog Module
The Blog Module is designed to manage blog posts, including creating, updating, retrieving, and deleting them. It also supports a moderation workflow, where changes to blog posts can be reviewed before approval.
Package Structure and Components
BlogPostDto
This class is used to transfer blog data between the client and server. Key attributes include:
BlogPost
This is the database representation of a blog post:
title, content: : Approved blog content.
pendingTitle, pendingContent: Moderation-pending content.
Recommended by LinkedIn
status: Moderation status (Status enum).
author: Stores the author's details using AppUserDto.
Status
Defines the state of a blog post:
BlogPostMapper
Maps between BlogPost (entity) and BlogPostDto:
The mapper ensures:
BlogPostService
Defines business logic for blog management. Core methods:
BlogPostServiceImpl
Implements the above methods with specific logic:
Blog Workflow
1. Creating a Blog Post
Updating a Blog Post
Moderation
Deleting a Blog Post
App- Module
The app-service module acts as the entry point for handling API requests. It includes controllers that interact with the services and manage the HTTP layer of the application. This module integrates different functionalities like account management and blog operations, providing endpoints for external clients.
AccountController
This controller handles account-related functionalities like login, registration, and fetching user profiles. It interacts with the AccountService.
Key Methods:
profile:
register:
login:
BlogPostController
This controller manages blog post operations, such as creating, reading, updating, and deleting blog posts. It interacts with the BlogPostService.
Key Methods:
createBlogPost:
getBlogPostById:
getAllBlogPosts:
updateBlogPost:
deleteBlogPost:
API Workflows
Each module in this platform is designed with scalability and maintainability in mind. By separating concerns into distinct modules, the system ensures cleaner code, easier debugging, and improved developer productivity.
GitHub Repository: Access the full source code here.
Feel free to explore the codebase, and let me know if you have any questions or need further enhancements. Happy coding! 🚀
Please visit next blog based on Asynchronous Real-Time Content Moderation System Using Amazon Bedrock and Spring Boot
Building CARS24 | Ex-PropertyGuru | Ex-Daffodil | Tech Lead | Senior Dev | MERN DEV | React | Node | Next | Mongo | GatsBy | Ejs | NeXtJS | MS SQL | JavaScript | Typescript | Elastic Search | Tailwind | NestJs | n8n
4moVery informative