Overview
A small e-commerce catalogue and cart. The React front end owns the interface, and a Spring Boot API shapes its payloads for that front end. You can browse with category filters, name search, whitelisted sorting and pagination, open a product, and add to a cart that reserves stock inside a transaction.
It is a portfolio build with no users and no real customers, and its API tests run against a real database instead of a mock.
What it does
- Money is an integer in minor units. The price crosses the wire as a long and the front end formats it without doing arithmetic. Totals are computed on the server, so a cart total and a checkout total cannot disagree.
- Cart lines keep the price from when they were added, so a later catalogue change cannot quietly re-price a cart someone is looking at.
- Overselling is refused in three places.
Product.reservewill not take more than the stock, so no caller can assign the field directly. A database check backs it, and an optimistic lock stops two concurrent checkouts decrementing the same row. - List and detail payloads differ on purpose. The summary record has no description component at all, and a test asserts that, instead of trusting a comment.
- Flyway owns the schema and JPA only validates it, so a mapping that drifts from a migration fails at boot. It did that on the first run, with a
char(3)column in SQL against avarchar(3)in the entity. - One error envelope. Every failure returns a code, a message and per-field errors, so the front end branches on one shape.
What I measured
The API has 37 tests that run against a real PostgreSQL 17 container through Testcontainers, and the front end has 25 Vitest tests. I re-ran both for this page and they pass at those counts. A Playwright run drives the real interface against the real API for 14 checks, including that no control escapes a 390 px viewport.
The N+1 guard is the test I would point to. The product's category is a lazy relation, so mapping a page of products would issue one extra SELECT per distinct category. The repository query carries an entity graph to make it a single join, and a test counts the statements Hibernate actually prepared instead of trusting the annotation. With the entity graph the page costs 2 statements. When I deleted the entity graph the count went to 5 and the test failed. The bug scales with the number of distinct categories, not with row count, so the endpoint returns identical JSON either way and stays fast on a six-row seed while getting slower on a real catalogue. That is why it needs a counting test.
What broke
A green build that ran nothing. Every test class here is named *IT, and Maven Surefire's default patterns are *Test, Test*, *Tests and *TestCase. So ./mvnw test printed BUILD SUCCESS with Tests run: 0 and looked completely healthy. I only caught it by reading the Tests run line instead of the build status. The pom now lists the includes explicitly and fails if a specified test matches nothing.
Two failures that only appeared once deployed. Changing an environment variable on Render does not restart the service, so the new CORS origin sat unused and preflight requests kept returning 403. That looks like a code fault and is not one. And my browser suite passed locally but failed against the deployed instance, because fixed waitForTimeout sleeps had baked in localhost latency. Polling the actual condition fixed it, and the suite then passed 14 of 14 against the live site.
What it does not do
There is no demo link on this page. The API runs on a free tier that sleeps, and when I last checked it was not answering, so I have left the link off. There is no authentication, no payment step and no order history, so the cart is where the flow stops.
Tech Stack
Front end: React, TypeScript, Tailwind CSS, TanStack Query, Vite API: Java, Spring Boot, Spring MVC, Spring Data JPA, Bean Validation Database: PostgreSQL, schema owned by Flyway Testing: JUnit with Testcontainers, MockMvc, Vitest with React Testing Library, Playwright Infrastructure: Docker (multi-stage image, non-root), Docker Compose for local Postgres