This long-form deep dive unpacks how ForkMyFolio’s backend turns a simple personal portfolio into a multi-user, slug-driven, production-ready platform built on Java 21 and Spring Boot 3.

What You’ll Learn

  • Layered Spring Boot Architecture

    How controllers, services, repositories, DTOs, and mappers fit together.

  • Multi-User, Slug-Driven Design

    Designing users, UUIDs, and slugs so one backend can host many portfolios safely.

  • Security and Roles

    JWT auth with refresh cookies, OAuth2 login, and role-based zones.

  • Portfolio Features

    PDF / Markdown / vCard exports, analytics, contact messages, and backups.

1. From “Just a Portfolio” to a Small SaaS

Most developer portfolios start as a static page. Then reality happens: you want a projects gallery, a blog, analytics,
a downloadable PDF resume, maybe even multi-user support for friends or clients. At that point you’re quietly building
a small SaaS whether you meant to or not.

ForkMyFolio embraces this reality from day one. Instead of hard-coding a single person’s site,
the backend is designed as a multi-user, slug-driven portfolio backend with:

  • Multiple users, each with their own portfolio and public slug
  • A clear separation between public, authenticated, and admin APIs
  • JWT authentication with secure refresh tokens in HttpOnly cookies
  • Dynamic portfolio configuration and PDF template selection
  • Non-intrusive visitor analytics and robust backup / restore flows

The frontend is a single-page application (SPA). The backend is a clean, versioned REST API that enforces the rules,
handles security, and keeps the data model consistent.

2. High-Level Architecture: A Clean, Layered Spring Boot Core

Under the hood, ForkMyFolio is a classic layered Spring Boot application with a strong opinion about boundaries.
The documentation in TECHNICAL_DOCUMENTATION.md and ARCHITECTURE_RULES.md formalizes this into
project-wide rules, but the core idea is simple:

Controllers handle HTTP and DTOs. Services handle business logic and entities. Repositories handle persistence. Never mix these responsibilities.

ForkMyFolio Architecture Rules

2.1 Technology Stack

Concern Technology
Language & Runtime Java 21
Application Framework Spring Boot 3 (Web, Security, Validation, OAuth2 Client)
Persistence Spring Data JPA, MySQL (with Flyway), H2 for development
Security Spring Security, JWT via jjwt, OAuth2 Login
API Documentation Springdoc OpenAPI (Swagger UI)
PDF Generation iText 7
Packaging & Deployment Fat JAR via Maven, Dockerfile + docker-compose

2.2 Package Layout

The code lives under the root package com.forkmyfolio and is split into focused subpackages:

  • config

    Spring configuration such as SecurityConfig, OpenAPI configuration, etc.

  • controller

    REST controllers grouped by resource: portfolios, auth, admin, user profile.

  • model & repository

    JPA entities and Spring Data JPA repositories that map to the underlying database.

  • service & mapper

    Business logic and manual DTO mappers that keep the API surface explicit and stable.

  • security

    JWT filters, authentication entrypoints, OAuth2 integration, and custom user details.

3. Layered Design and One-Way Data Flow

The value of the architecture is not just in package names; it’s in how requests actually move through the system.

3.1 The Three Layers

  • Controller Layer@RestController classes that speak HTTP and JSON, accept and return DTOs, and never call the database directly.
  • Service Layer@Service components that encapsulate business rules, operate on JPA entities, and orchestrate multiple repositories.
  • Repository Layer@Repository interfaces powered by Spring Data JPA, responsible only for persistence.

A typical request flows like this:

Request Flow

HTTP Request (JSON DTO) → Controller → Service → Repository → Database → Service → Controller →
HTTP Response (DTO wrapped in ApiResponseWrapper)

Even for file-heavy features like PDF or vCard downloads, controllers remain thin: they set headers and content type,
while services prepare the domain data and call specialized generators.

4. Designing for Multi-User Slugs and UUIDs

ForkMyFolio V2 pivots from a single-portfolio mindset to a true multi-user platform. That decision ripples through the
data model, URL design, and security rules.

4.1 The User Entity as the Anchor

The User entity in com.forkmyfolio.model is more than a login record; it is the root of a user’s
entire portfolio universe:

  • Internal primary key: numeric id (never exposed publicly).
  • External identifier: uuid, used in APIs and backups.
  • Public routing identifier: slug, used in URLs like /api/v1/portfolios/{slug}.
  • Authentication details: email, password, AuthProvider, providerId.
  • Lifecycle flags: active, emailVerified, termsAcceptedAt, termsVersion.
  • Relations to portfolio content: profile, projects, experiences, skills, qualifications, testimonials, contact messages, settings, refresh tokens.
  • Roles via Set<Role>, mapped to Spring Security authorities like ROLE_USER, ROLE_ADMIN.

Because User implements UserDetails, it plugs directly into Spring Security. The role set becomes a
collection of GrantedAuthority instances, which SecurityConfig then uses when enforcing access rules.

4.2 UUIDs and Slugs as Public Contracts

The architecture rules explicitly require that raw database IDs never appear in the public API. Instead:

  • All public-facing references use UUIDs for entities.
  • Public portfolio routing uses a human-readable slug per user.

This prevents simple enumeration attacks (guessing /users/1, /users/2, etc.) and makes URLs
stable across database migrations.

4.3 PortfolioService.getPublicPortfolioUserBySlug

Public portfolio endpoints never query the User table directly. They instead call a single, well-defined
service method:

User getPublicPortfolioUserBySlug(String slug)

This method guarantees two things before any data is returned:

  • The user exists and is active, otherwise a ResourceNotFoundException is thrown.
  • The user’s portfolio is public, otherwise a PermissionDeniedException is thrown.

By centralizing that logic, the platform makes it impossible for a controller to accidentally leak private portfolios
just by forgetting a filter.

5. Public, User, and Admin API Zones

The API surface is deliberately split into three access zones, each with different guarantees and responsibilities.

5.1 Public Zone

Base paths like:

  • /api/v1/portfolios/{slug} – portfolio shell and section summaries
  • /api/v1/portfolios/{slug}/projects, /experience, /skills, /qualifications, /testimonials
  • /api/v1/portfolios/{slug}/pdf, /markdown, /vcard for exports
  • /api/v1/portfolios/{slug}/contact-messages for contact form submissions
  • /api/v1/settings, /api/v1/settings/pdf-templates for global public settings
  • /api/v1/policies/... for privacy policy and terms of service

In SecurityConfig, these routes are explicitly marked permitAll() for the relevant methods (GET for
most, POST for contact messages). That makes the public zone browseable without credentials while keeping
sensitive actions behind auth.

5.2 Authenticated User Zone

Authenticated users operate primarily under /api/v1/me and /api/v1/auth:

  • /api/v1/auth/register, /login, /refresh-token, /logout
  • /api/v1/me for account details and password changes
  • /api/v1/me/profile for portfolio profile
  • /api/v1/me/projects, /experiences, /skills, /qualifications, /testimonials for content management
  • /api/v1/me/settings for per-user configuration
  • /api/v1/me/contact-messages as a personal inbox
  • /api/v1/me/backup for self-service backup and restore

These endpoints enforce ownership: a user can only touch their own portfolio data, even if they know someone else’s
UUID or slug.

5.3 Admin Zone

Anything under /api/v1/admin is restricted to ROLE_ADMIN users:

  • User management (/users)
  • Global settings (/settings)
  • Site-wide contact message moderation
  • Visitor statistics and analytics (/stats)
  • Full-system backup and restore, including per-user restores

This zone is designed with operational tasks in mind: migrations, incident recovery, and platform-wide tuning.

6. Security: JWT, Refresh Cookies, and OAuth2

Security is not an afterthought in ForkMyFolio; it’s baked into the architecture. The combination of JWTs,
HttpOnly cookies, and Spring Security makes authentication predictable and auditable.

6.1 JWT and Refresh Flow

The application uses a dual-token strategy:

  • Access token — short-lived JWT sent in the Authorization: Bearer <token> header.
  • Refresh token — longer-lived, stored in an HttpOnly, Secure cookie.

When a user authenticates via /auth/login or /auth/register, the backend returns:

  • A JSON payload with the access token.
  • A Set-Cookie header that stores the refresh token in an HttpOnly cookie.

Once the access token expires, the frontend calls /auth/refresh-token. The browser automatically sends the
refresh cookie, the backend validates it, issues a new access token, and rotates the refresh token (rolling tokens).

6.2 SecurityConfig in Practice

The SecurityConfig class wires everything together:

  • Disables CSRF for the pure JSON API surface.
  • Sets session creation policy to stateless.
  • Registers a JwtAuthenticationFilter before the standard username/password filter.
  • Configures an JwtAuthenticationEntryPoint to handle unauthorized errors consistently.
  • Wires up OAuth2 login using CustomOAuth2UserService and custom success/failure handlers.

6.3 CORS and Cookies

Given that the frontend often runs on a different origin (e.g., http://localhost:3000 in development),
CORS and cookie configuration are crucial. The corsConfigurationSource bean:

  • Reads allowed origins from app.cors.allowed-origins.
  • Enables credentials so the refresh cookie can be sent.
  • Whitelists the necessary headers and exposes Content-Disposition for file downloads.

7. DTOs, Manual Mappers, and Response Wrappers

Rather than exposing JPA entities directly over the wire, ForkMyFolio embraces DTOs everywhere.
The mapper package contains hand-written mappers that translate between
entities and API-friendly representations.

7.1 Why Manual Mappers?

  • Explicitness — you see exactly which fields are exposed.
  • Contextual enrichment — experience and project DTOs can include computed views like skill proficiency.
  • Controlled evolution — as the API evolves, mappers make it obvious what changed and why.

7.2 A Uniform Response Envelope

Every controller returns an ApiResponseWrapper<T>, so the frontend always deals with the same shape.

{
  "status": "success",
  "data": { ... },
  "errors": []
}

or, on failure:

{
  "status": "fail",
  "data": null,
  "errors": [
    { "field": "email", "message": "Email is already in use" }
  ]
}

8. Portfolio Content, Settings, and Exports

The heart of ForkMyFolio is the portfolio itself. The backend models portfolio data with a set of focused entities:

  • PortfolioProfile — name, headline, summary, social links.
  • Project — title, description, tech stack, links, highlights.
  • Experience — work or volunteer history.
  • Qualification — education and certifications.
  • UserSkill — user-specific skill relations often mapped to platform-wide skills.
  • Testimonial — quotes from collaborators and clients.

Public /portfolios/{slug}/... endpoints aggregate these into read-optimized DTOs, enforcing visibility
and sort order. Hidden or draft content stays private.

8.1 Dynamic Settings and Feature Flags

Settings exist at two levels:

  • Global settings controlled by admins.
  • User settings that override specific behaviors per portfolio.

The /api/v1/me/settings endpoint returns the effective settings, combining both sources so the frontend
doesn’t need to merge them manually.

8.2 PDF, Markdown, and vCard Downloads

Visitors can export a portfolio in several formats:

  • PDF — a resume-style representation powered by iText 7.
  • Markdown — a portable document format ideal for GitHub READMEs or static site generators.
  • vCard — a quick import format for contact apps.

Each export endpoint follows the same pattern: controller handles HTTP details, service composes the data and calls
a generator, then the controller streams the result.

9. Analytics, Contact Messages, and Backups

9.1 Non-Intrusive Analytics

ForkMyFolio tracks key events (page views, project views, engagement) without resorting to invasive tracking.
The goal is to give portfolio owners actionable insights while respecting visitor privacy.

9.2 Contact Messages

Contact messages start in the public zone:

  • Visitors submit a message via /api/v1/portfolios/{slug}/contact-messages.
  • The backend resolves the slug, stores a ContactMessage linked to the owning user.
  • The owner reads and manages their inbox via /api/v1/me/contact-messages.

9.3 Backup and Restore

The platform treats portfolios as first-class data that users should be able to take with them:

  • Users can download a full JSON backup of their portfolio and restore from it later.
  • Admins can generate system-wide backups, restore single users, or restore the entire platform in a disaster scenario.

10. Profiles, Environment Variables, and Docker

Environment-driven configuration ensures the same codebase runs safely in development and production.

  • Development profile — H2 in-memory database, developer-friendly CORS, and a non-sensitive JWT secret.
  • Production profile — MySQL/PostgreSQL, strict CORS, secure cookies, and a strong JWT secret from the environment.

Running locally is as simple as mvn spring-boot:run. For production, a Docker image wraps the app with the
right environment variables for database, JWT, and CORS settings.

11. Lessons Learned

ForkMyFolio’s backend shows what happens when you treat a “simple portfolio” as a platform from the start:

  • Separating public, user, and admin zones keeps permissions comprehensible.
  • Leaning into DTOs and manual mappers makes the API easy to evolve safely.
  • Using UUIDs and slugs avoids migration headaches and security pitfalls.
  • Building backup and export paths in early makes users trust the system with their data.
  • Taking security seriously (JWTs, refresh cookies, OAuth2, proper CORS) makes frontend integration a lot less painful.

If you’re designing your own multi-user app on Spring Boot, ForkMyFolio offers a concrete blueprint: start with
clean boundaries, respect your users’ data, and let your backend grow from “my portfolio” into “our platform” without
losing control.