REST API starter template using Spring Boot + PostgreSQL with a feature-based modular structure (auth, user, file).
- Spring Security + JWT
- Register, login, refresh token
- Access token via
Authorization: Bearer <token> - Refresh token stored in an HttpOnly cookie (
refresh_token)
- User profile setup
- Update
firstName,lastName, optionalavatar(uploaded file id) - User list endpoint returns only users with completed profiles
- Update
- Flyway migrations (
src/main/resources/db/migration) - Docker Compose for PostgreSQL (
docker-compose.yml) - OpenAPI / Swagger UI
- Swagger UI:
http://localhost:8222/swagger-ui/index.html
- Swagger UI:
- Java: 21
- Spring Boot: 4.x
- Database: PostgreSQL
- Migrations: Flyway
- Auth: Spring Security + JJWT
- API docs: springdoc-openapi
docker compose up -dThis starts Postgres on localhost:5432 using the credentials in docker-compose.yml and src/main/resources/application.yaml.
./mvnw spring-boot:runAPI runs on http://localhost:8222.
http://localhost:8222/swagger-ui/index.html
Main config lives in src/main/resources/application.yaml:
- DB:
spring.datasource.* - Flyway:
spring.flyway.* - Uploads:
application.storage.type:localapplication.storage.upload-dir:./uploads- Static serving:
GET /uploads/**(mapped to the local uploads folder)
- JWT secret:
application.security.secret
- Login returns:
- JSON body with access token
Set-Cookie: refresh_token=...; HttpOnly; Path=/; ...
- Use the access token for protected endpoints:
Authorization: Bearer <accessToken>
- Refresh endpoint reads the refresh token from the
refresh_tokencookie and returns a new access token + refreshed cookie.
AuthController sets refresh cookie with .secure(true). Browsers will typically not store Secure cookies on plain HTTP.
- For local testing with a browser, either:
- run behind HTTPS, or
- change cookie
.secure(false)for local only
- If you use a tool like curl/Postman and manually carry cookies, behavior may differ.
Base URL: http://localhost:8222
- POST
/api/v1/auth/register- Body:
{ "email": "...", "password": "..." } - Response:
201
- Body:
- POST
/api/v1/auth/login- Body:
{ "email": "...", "password": "..." } - Response:
200+ refresh cookie + body:email,accessToken,isCompletedProfile
- Body:
- GET
/api/v1/auth/refresh- Reads refresh token from cookie
refresh_token - Response:
200+ new refresh cookie + new access token
- Reads refresh token from cookie
- PUT
/api/v1/users- Body:
{ "firstName": "...", "lastName": "...", "profileImageId": "optional-file-id" } - Marks user profile as completed
- If
profileImageIdis provided, the file is moved fromTEMPtoUSER_AVATAR
- Body:
- GET
/api/v1/users/{email}- Returns user details by email
- GET
/api/v1/users?page=1&size=5- Returns paginated list of users with completed profiles only
- POST
/api/v1/files(multipart/form-data)- Field:
file - Validations:
- Max size: 5MB
- Allowed types:
image/png,image/jpeg,image/webp
- Response:
201with:fileId,url(public URL under/uploads/...)
- Field:
src/main/java/com/naarith/starter
ββ config/ # Cross-cutting configuration (OpenAPI, scheduling, logging filter)
ββ entity/ # Shared base entity
ββ exception/ # Shared exception + error response model
ββ features/
β ββ auth/ # Auth (JWT) feature module
β ββ user/ # User/profile feature module
β ββ file/ # File upload/storage feature module
ββ utils/ # Small shared utilities
Migrations are in src/main/resources/db/migration and run automatically at startup:
V1__init.sql: enables useful PostgreSQL extensions (pg_trgm,unaccent)V2__create_users_table.sql: createsuserstable + trigram indexes for searchV3__create_files_table.sql: createsfilestable + FKfiles.uploaded_by -> users.uidV4__add_foreign_key_for_users.sql: FKusers.profile_image_id -> files.id
src/main/java/com/naarith/starter/SpringBootStarterApplication.java: Spring Boot application bootstrap class.
config/OpenApiConfig.java: configures OpenAPI metadata + Bearer JWT scheme, and injects a sharedApiErrorschema into 4xx/5xx responses (also adds 401/403 to secured operations).config/RequestLoggingFilter.java: logs method, path, status, and request duration for each request.config/SchedulingConfig.java: enables Spring scheduling (@EnableScheduling) used by background jobs like file cleanup.
exception/ApiError.java: standardized error payload model (used by exception handlers and security handlers).exception/BaseException.java: base runtime exception carrying anHttpStatus.exception/GlobalExceptionHandler.java: centralized exception handling forBaseExceptionand validation errors (MethodArgumentNotValidException).exception/ResourceNotFoundException.java:404exception used across features when something canβt be found.
entity/BaseEntity.java: providescreatedAt/updatedAttimestamps via JPA lifecycle hooks.
features/auth/controller/AuthController.java: register/login/refresh endpoints; sets refresh token inHttpOnlycookie.features/auth/dto/RegisterReqDTO.java: request payload for register (email + password validation).features/auth/dto/LoginReqDTO.java: request payload for login.features/auth/dto/LoginResDTO.java: login/refresh response payload (email, access token, profile completion flag).features/auth/enums/TokenType.java: differentiates access vs refresh tokens.features/auth/exception/InvalidCredentialsExceptions.java: thrown when login fails (bad credentials or user not found).features/auth/exception/TokenExpiredException.java: thrown when JWT is expired.features/auth/exception/TokenInvalidException.java: thrown when JWT is invalid (bad signature/type/user no longer exists).features/auth/security/SecurityConfig.java: Spring Security filter chain (public auth endpoints + Swagger +/uploads/**, everything else secured) with custom 401/403 JSON responses.features/auth/security/JwtAuthenticationFilter.java: extracts and validates Bearer access tokens and setsSecurityContext.features/auth/security/CustomUserDetailsService.java: loads user details fromUserServicefor authentication.features/auth/security/UserPrincipal.java:UserDetailsimplementation wrappingUserDTO(grantsUSERauthority).features/auth/service/AuthService.java: register/login/refresh business logic (password encoding + token generation).features/auth/service/JwtService.java: generates and validates JWTs (access: 10 min, refresh: 60 min) and enforcestokenTypeclaim.features/auth/service/model/LoginResult.java: internal service return type combiningLoginResDTO+ refresh token string.
features/user/controller/UserController.java: endpoints for updating current user, fetching user details by email, and listing users with completed profiles.features/user/dto/UserDTO.java: internal DTO representing a user (used for auth principal and persistence mapping).features/user/dto/UserUpdateReqDTO.java: request payload for profile update (first/last name + optional profile image id).features/user/dto/UserDetailsResDTO.java: user details response DTO (includes profile image URL when available).features/user/dto/UserListReqDTO.java: pagination request for listing users (defaults to page=1, size=5).features/user/dto/UserListResDTO.java: paginated user list response.features/user/entity/User.java: JPA user entity (NanoId id, email, password hash, names, completed flag, optional profile image relation).features/user/repository/UserRepository.java: JPA repository withEntityGraphto fetch profile image when loading users.features/user/service/UserService.java: user CRUD + profile completion logic; integrates withFileServiceto move avatar out of TEMP and delete old avatar.features/user/mapper/UserMapper.java: maps between entity and DTO/response; builds public upload URLs viaUtils.features/user/exception/EmailAlreadyExistsException.java: thrown when registering with an existing email.features/user/exception/UserNotFoundException.java: thrown when user lookup fails.features/user/exception/InvalidProfileImageException.java: thrown when provided profile image id is missing or not usable.
features/file/controller/FileController.java: file upload endpoint (multipart); currently focuses on upload.features/file/dto/FileUploadResDTO.java: upload response (file id + public URL).features/file/entity/File.java: JPA file entity (NanoId id, storage path, mime/type/size, usage type, uploaded by user).features/file/enums/UsageType.java: file usage classification (TEMP,USER_AVATAR) used for folder placement and lifecycle.features/file/repository/FileRepository.java: file repository plus queries for cleanup (unused TEMP files older than cutoff) and path lookup.features/file/mapper/FileMapper.java: maps file entity to response and builds public URL viaUtils.features/file/config/FileProperties.java: bindsapplication.storage.*configuration properties (e.g., upload dir).features/file/config/FileWebMvcConfig.java: maps/uploads/**to the local filesystem upload directory for static serving.features/file/service/StorageService.java: storage abstraction (store/load/delete/move/exists).features/file/service/LocalStorageService.java: local filesystem storage implementation (tmp + per-usage folders).features/file/service/FileValidator.java: validates uploaded images (size/type/non-empty).features/file/service/FileService.java: orchestrates upload, DB persistence, and moving/deleting files.features/file/service/FileCleanupJob.java: scheduled job deleting TEMP files older than 24 hours (runs hourly).features/file/service/model/StoredFile.java: internal model representing stored file metadata after saving.features/file/exception/InvalidFileException.java: thrown when upload validation fails.features/file/exception/StorageException.java: thrown on storage-layer failures (store/load/move).
utils/Utils.java: helper for building absolute URLs (used to expose upload URLs in API responses).
