Case study
Finance Dashboard API
A Spring Boot REST API for personal finance tracking, with a security layer that returns proper 401/403 JSON instead of Spring's default HTML redirects, and JPA projections that push aggregation into SQL.
- Role
- Solo
- Timeline
- Stack
- JavaSpring BootSpring SecuritySpring Data JPAMySQLH2springdoc-openapi
- Links
- Source
Problem
A REST API for personal finance tracking needs to serve dashboard aggregations — summary totals, monthly trends, category breakdowns — behind real authentication. The easy way to build that in Spring Boot produces an API that quietly assumes its only client is a browser.
Context
A solo project, ~1,850 lines of Java across 36 files: Spring Boot, Spring Security, Spring Data JPA, MySQL in production and H2 for tests, and springdoc-openapi for docs.
Decisions
A real security layer, not just a dependency. Spring Security's default
unauthenticated-request behavior is an HTML redirect to a login page — fine
for a browser, useless for an API client, which just sees a redirect where
it expected JSON. This service replaces that default with a
RestAuthenticationEntryPoint and RestAccessDeniedHandler pair , backed by a custom UserDetailsService
against the app's own User entity.
Aggregation pushed into SQL. The dashboard's category totals and
monthly trends are computed via interface-based JPA projections
(CategoryTotalProjection, MonthlyTrendProjection) . Only the
columns a projection actually needs cross the wire — a real performance
decision, not a default.
Split request and response DTOs. UserRequest, UserUpdateRequest, and
UserResponse are separate types rather than one class reused in both
directions, sub-packaged by concern (dto/record/, dto/user/,
dto/dashboard/). That separation is what — it can't serialize what the
response type doesn't have a field for.
Implementation
@ControllerAdvice-based global exception handling maps typed exceptions
(ResourceNotFoundException, BadRequestException) to a consistent error
schema. The package structure is genuinely layered —
controller/service/repository/model/dto/security/config/exception — rather
than one flat package. Integration tests run against H2 with
spring-security-test wired in, alongside the standard application context
test.
What I'd change
The repo doesn't ship a Dockerfile yet, which is the difference between
"clone and read" and "clone and run" for anyone evaluating it. That's next.