A learning-focused REST API that practices contract-first design with Spring Boot, PostgreSQL, and generated OpenAPI contracts against a realistic webshop data model.
| 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 |
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.
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.
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.
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.
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.
openApiGenerate {
generatorName.set("spring")
inputSpec.set("$rootDir/src/main/resources/webshop-v1.0.yaml")
outputDir.set(layout.buildDirectory.dir('generated').get().toString())
apiPackage.set("com.giusniyyel.openapi.api")
modelPackage.set("com.giusniyyel.openapi.models")
configOptions.set([
delegatePattern : 'true',
useResponseEntity: 'false',
interfaceOnly : 'true',
useSpringBoot3 : 'true'
])
}I chose interface-only generation so my controllers could stay explicit while still inheriting the contract shape from the spec.
The service layer sets created and updated timestamps on write operations, keeping that concern out of the controller and closer to application rules.
public Customer save(Customer customer) throws CustomerPersistenceException {
if (customer.getId() != null) {
customer.setUpdated(OffsetDateTime.now());
} else {
customer.setCreated(OffsetDateTime.now());
customer.setUpdated(OffsetDateTime.now());
}
return customerRepository.save(customer);
}This gave me a simple convention for distinguishing inserts from updates without pushing timestamp logic into the persistence adapter.
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.
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.
Springdoc OpenAPI serves Swagger UI, and Spring Actuator exposes runtime endpoints. Together they made local development and manual verification straightforward during prototyping.
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:
webshop-v1.0.yamlCustomer modelsCrudRepositoryThe repository adapter wraps database failures and missing records in project-specific exceptions:
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.
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.
date, and timestamp with time zone fields required explicit conversion logic between JPA and OpenAPI modelsThis 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.
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.
A contract-first REST API built with Spring Boot to explore API design, code generation, and layered backend architecture against a real PostgreSQL webshop schema.
Dec 2023 – Dec 2023