A learning-focused REST API that practices contract-first design with Spring Boot, PostgreSQL, and generated OpenAPI contracts against a realistic webshop data model.
Snapshot
Field
Details
Type
Backend Rest API prototype
Context
Learning experiment
Role
Solo developer
Year
2023
Status
Completed prototype
Main focus
Contract-first API design, customer CRUD, and layered persistence
Overview
This project is a Spring Boot REST service built around a contract-first workflow. I defined the API in OpenAPI first, generated Spring interfaces and models from that contract, and implemented the customer resource end to end against a PostgreSQL webshop schema.
The database models a full e-commerce domain with customers, addresses, products, orders, stock, and related entities. The implemented API covers customer operations only, which made the project a focused way to practice specification-driven development, persistence mapping, and testable service design without trying to ship an entire storefront backend at once.
The problem
Building backend services from implementation details first often leads to drift between what clients expect and what the server actually exposes. For a webshop domain with several related entities, that mismatch becomes harder to manage as the API grows.
API contracts were easy to change informally without a single source of truth
Generated models and hand-written persistence code needed a clear boundary
PostgreSQL enum types and timestamp fields did not map cleanly to OpenAPI models
A full webshop schema existed, but the first slice still needed a maintainable architecture
Who it was for
Developers learning contract-first API development with Spring Boot
API consumers that need predictable customer CRUD endpoints
Future backend work extending the same OpenAPI spec to products, orders, and addresses
Anyone reviewing how generated API contracts connect to JPA persistence
My role
I owned the project end to end: the OpenAPI specification, Gradle code generation setup, Spring Boot application structure, customer persistence layer, REST controllers, exception handling, database integration, and unit tests. I also scoped the first implementation to the customer resource while leaving the broader webshop contract prepared for later expansion.
What the project does
The service exposes versioned customer endpoints under /api/v1/customers, backed by the webshop.customer table in PostgreSQL. Requests flow through controller, service, repository adapter, and JPA layers, with MapStruct handling translation between generated API models and database entities.
Lists all customers
Retrieves a customer by ID
Creates a new customer
Updates an existing customer
Serves interactive API docs through Swagger UI
Exposes Spring Actuator endpoints for runtime inspection
Key features
Contract-first API generation
The OpenAPI spec is the starting point for the server surface area. Gradle generates Spring interfaces and model classes before compilation, so the implementation stays aligned with the declared contract.
This gave me a simple convention for distinguishing inserts from updates without pushing timestamp logic into the persistence adapter.
Layered persistence with MapStruct
Generated OpenAPI models, JPA entities, and PostgreSQL column types do not match by default. I used MapStruct to translate between API models and CustomerEntity, including custom conversions for dates and timestamps.
I kept generated models at the edge of the system and treated JPA entities as infrastructure details, which made the repository adapter easier to reason about and test.
PostgreSQL webshop schema integration
The project uses a realistic webshop schema with enums, foreign keys, and related tables for products, orders, stock, and addresses. The customer slice connects to that schema directly rather than using an in-memory or simplified demo database.
API documentation and observability
Springdoc OpenAPI serves Swagger UI, and Spring Actuator exposes runtime endpoints. Together they made local development and manual verification straightforward during prototyping.
Technical approach
The application follows a layered structure: REST controllers in infrastructure, application services for use cases, a domain port for customer persistence, and adapter classes that implement that port with Spring Data JPA.
Build flow:
OpenAPI Generator creates models and API interfaces from webshop-v1.0.yaml
Controllers call application services with generated Customer models
Services apply business rules such as timestamp handling
Repository adapters map to JPA entities and persist through CrudRepository
MapStruct converts between API and persistence representations
The repository adapter wraps database failures and missing records in project-specific exceptions:
java
public Customer getCustomerById(Integer id) {
CustomerEntity customer = customerCrudRepository.findById(id).orElseThrow(
() -> new ResourceNotFoundException("Customer not found")
);
return mapper.toCustomer(customer);
}
I mapped PostgreSQL gender enums through a JPA attribute converter so database values stayed compatible with the generated OpenAPI enum. For testing, I added Mockito-based unit tests for the service and controller layers, covering list retrieval, lookup success and failure, and create/update timestamp behavior.
Security dependencies are present in the build but not enabled in the running prototype, which kept the first slice focused on API and persistence mechanics.
Design decisions
I optimized for a clear first vertical slice rather than partial coverage across every webshop entity. The OpenAPI file already sketches addresses, products, orders, and stock, but only customer paths are active in the running service.
Started from OpenAPI instead of annotating controllers after the fact
Used a port-and-adapter style boundary around customer persistence
Kept generated models at the API edge and JPA entities in infrastructure
Centralized date and timestamp conversion in MapStruct rather than manual mapping in services
Exposed Swagger UI and Actuator to support local exploration during development
Left broader webshop endpoints commented in the spec to define future scope without overbuilding the prototype
Challenges and tradeoffs
PostgreSQL enums, date, and timestamp with time zone fields required explicit conversion logic between JPA and OpenAPI models
The OpenAPI contract describes more of the webshop than the current implementation exposes, which is useful for planning but can look incomplete from the outside
POST and PUT share one controller method, which simplified early development but is less idiomatic than separate create and update handlers
Exception handling returns plain text messages for some error cases instead of structured problem details
Spring Security is included only as a commented dependency, so authentication and authorization were intentionally out of scope
Integration tests against a live PostgreSQL instance were not part of this version, so coverage stays at the unit level
What I learned
This project helped me understand why contract-first development is useful when the API surface needs to stay explicit from the start. Generating models and interfaces early forced me to think about request and response shapes before getting lost in JPA details.
How to wire OpenAPI Generator into a Gradle build for Spring Boot 3
How to keep generated API types separate from persistence entities using MapStruct
How a small hexagonal boundary makes repository swaps and unit testing easier
That a realistic schema is valuable for learning, even when the first implementation only covers one resource
Current status
This project is no longer maintained and should be understood as a completed learning prototype. It demonstrates contract-first API design and customer persistence against a PostgreSQL webshop schema, but it does not represent a production-ready storefront backend. I keep it in my portfolio because it shows how I approach API contracts, generated code, and layered backend structure in a domain with more complexity than a toy CRUD example.
If I revisited this today
Implement the remaining webshop resources incrementally, one OpenAPI tag at a time
Split create and update endpoints and align HTTP status codes with REST conventions
Add integration tests with Testcontainers so persistence behavior is verified against PostgreSQL
Enable Spring Security with role-based access for admin versus public customer operations
Return structured error responses instead of plain text bodies
Add pagination, filtering, and validation rules to customer list and write endpoints
Publish the OpenAPI spec as a reusable artifact for frontend or mobile clients