Building a Robust Multi-Module Content Platform with Spring Boot and MongoDB

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:

  • Register and authenticate securely.
  • Create, edit, and delete blog posts.
  • Submit content for moderation and track its status.
  • Retrieve content efficiently with pagination and indexing.
  • Ensure database consistency with migrations and validations.

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


Article content

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

  1. Initialization: It starts the Spring Boot application by scanning and initializing all the required beans and configurations.
  2. Dependency Injection: Through annotations like @SpringBootApplication, it ensures that all components (services, repositories, etc.) are injected and ready to use throughout the platform.
  3. Database Management: It integrates Mongock for versioned database migrations, ensuring consistent schema updates across environments.
  4. Environment Configuration: Manages environment-specific configurations using profiles (e.g., application.yaml and application-local.yaml).
  5. Logging: Provides centralized logging configurations via logback-spring.xml, ensuring structured and traceable logs.


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);
    }
}        

  • JWT Decoder: Handles decoding and verification of tokens.
  • SecurityFilterChain: Configures request authorization rules and enforces stateless sessions.

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:

  • Users submit a RegisterDto.
  • Validation occurs, and a JWT token is generated upon successful registration.

Login:

  • Users submit a LoginDto.
  • Credentials are authenticated, and a JWT token is returned.

Profile Retrieval:

  • Extracts user details via Authentication and returns them as a DTO


Common Service


Article content

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:

  • The AppExceptionHandler ensures that all services handle exceptions in a consistent manner.
  • Converts internal exceptions into user-friendly API responses.

Shared Constants and Utilities:

  • Centralized storage of API endpoint constants and utility methods reduces duplication.
  • Updates to common logic (e.g., date formatting) are automatically reflected across all dependent modules.

Standardized API Responses:

  • ApiResponse ensures consistent response formatting for success and error cases.
  • Simplifies API testing and improves client-side integration.

Reusability in Entity Design:

  • The MongoBaseEntity promotes a consistent structure for MongoDB documents.
  • Inherits common fields and behaviors across multiple entities.

Benefits of the Common Module

  • Reusability: Centralizes common logic, reducing redundancy across services.
  • Consistency: Promotes uniformity in error handling, response formatting, and entity design.
  • Maintainability: Changes made in the common module automatically propagate to all dependent services.
  • Scalability: As the application grows, new utilities, constants, and shared logic can be added here without affecting existing services.


Blog Module

Article content

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:

  • id: Unique identifier for the blog post.
  • title and content: Represent the approved title and content of the blog post.
  • pendingTitle and pendingContent: Represent new titles or content awaiting moderation.
  • status: Tracks the moderation status (e.g., DRAFT, PENDING_MODERATION).
  • author: Contains author details (AppUserDto).

BlogPost

This is the database representation of a blog post:

  • Extends MongoBaseEntity for common MongoDB document fields like createdAt and updatedAt.
  • Key attributes:

title, content: : Approved blog content.

pendingTitle, pendingContent: Moderation-pending content.

status: Moderation status (Status enum).

author: Stores the author's details using AppUserDto.

Status

Defines the state of a blog post:

  • DRAFT: Initial, editable state.
  • PENDING_MODERATION: Awaiting approval.
  • APPROVED: Approved by moderators.
  • REJECTED: Rejected by moderators.

BlogPostMapper

Maps between BlogPost (entity) and BlogPostDto:

  • To Entity: Converts BlogPostDto to BlogPost.
  • To DTO: Converts BlogPost to BlogPostDto.

The mapper ensures:

  • Author details are mapped correctly.
  • Avoids exposing sensitive fields like MongoDB metadata directly in responses.

BlogPostService

Defines business logic for blog management. Core methods:

  • createBlogPost: Creates a new blog post.
  • getBlogPostById: Fetches a blog post by ID.
  • getAllBlogPosts: Retrieves all blog posts, filtered by status.
  • updateBlogPost: Updates a blog post with draft or moderation changes.
  • deleteBlogPost: Deletes a blog post, ensuring only the author can delete it.

BlogPostServiceImpl

Implements the above methods with specific logic:

  • Create:Extracts author details from Authentication.Saves the post with DRAFT status.
  • Update:Validates the author.If DRAFT, updates title and content directly.If PENDING_MODERATION, stores updates in pendingTitle and pendingContent.
  • Retrieve:Fetches posts by ID or status (with pagination).
  • Delete:Ensures only the post's author can delete it.

Blog Workflow

1. Creating a Blog Post

  • A user submits a BlogPostDto via the frontend.
  • The createBlogPost method:Extracts the authenticated user's details using UserUtil.Sets DRAFT status and saves the post.
  • The post is stored in the database and returned to the client.

Updating a Blog Post

  • The user sends an update request with the BlogPostDto.
  • The updateBlogPost method:Verifies that the user is the author.Handles updates based on the status:DRAFT: Direct updates to title and content.PENDING_MODERATION: Updates pendingTitle and pendingContent instead.
  • Saves the updated post and returns it.

Moderation

  • Moderators can review posts with PENDING_MODERATION status.
  • Depending on the moderation outcome, the post’s status is updated to APPROVED or REJECTED.

Deleting a Blog Post

  • A delete request is sent with the post ID.
  • The deleteBlogPost method:Ensures only the author can delete their post.Removes the post from the database.


App- Module

Article content

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:

  • Endpoint: GET /api/v1/account/profile
  • Retrieves the authenticated user's profile.
  • Delegates the logic to AccountService.getProfile().

register:

  • Endpoint: POST /api/v1/account/register
  • Handles user registration using RegisterDto.
  • Validates the input and calls AccountService.register()

login:

  • Endpoint: POST /api/v1/account/login
  • Handles user login using LoginDto.
  • Validates credentials and generates a JWT token through AccountService.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:

  • Endpoint: POST /api/v1/blog
  • Creates a new blog post.
  • Extracts the authenticated user's details using Authentication and passes them along with the blog post data to the BlogPostService.

getBlogPostById:

  • Endpoint: GET /api/v1/blog/{id}
  • Retrieves a blog post by its ID.
  • Calls BlogPostService.getBlogPostById() and wraps the result in a success response.

getAllBlogPosts:

  • Endpoint: GET /api/v1/blog
  • Fetches paginated blog posts filtered by moderation status (APPROVED by default).
  • Calls BlogPostService.getAllBlogPosts() with a pageable object and status filter.

updateBlogPost:

  • Endpoint: PUT /api/v1/blog/{id}
  • Updates an existing blog post.
  • Validates that the authenticated user is the author of the post.
  • Allows direct updates if the post is in DRAFT or sends updates for moderation.

deleteBlogPost:

  • Endpoint: DELETE /api/v1/blog/{id}
  • Deletes a blog post if the authenticated user is its author.
  • Calls BlogPostService.deleteBlogPost().

API Workflows

  • User Registration

Article content

  • User Login

Article content

  • User Profile

Article content

  • Create a Blog Post

Article content

  • Update a Blog Post

Article content

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

Vikrant Banwal

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

4mo

Very informative

Like
Reply

To view or add a comment, sign in

More articles by Prabhat Pankaj

Insights from the community

Others also viewed

Explore topics