Tasteprint
Tasteprint is a travel food passport. Users record dishes they have tried, see which parts of a food culture they have covered, receive a five-bite mission for an upcoming trip, and compare or combine their map with friends.
The product measures cultural breadth instead of restaurant visits. Each destination starts with six entry points: staple, street food, breakfast, sweet, drink, and signature dish. Importance weights keep a famous or defining dish more meaningful than a secondary discovery.
What is included
- Account registration, login, opaque bearer sessions, logout, editable profiles, and verified account deletion
- A curated atlas of 12 destinations and 72 dishes
- Tasting journal with ratings, notes, dates, places, coordinates, and validated photo uploads
- Weighted world and destination coverage
- Important missing dishes based on the user's current map and next trip
- Trips with a persisted five-bite mission and date-aware completion
- Shared destination challenges with invite codes and group progress
- Public Tasteprint pages with privacy controls
- Two-person taste map comparison and a suggested shared bite
- Responsive desktop and mobile interfaces
- OpenAPI documentation, health probes, Docker deployment, and CI
Architecture
Browser
|
v
Nginx, React single-page application
|
+---- /api and /uploads
|
v
Spring Boot modular monolith
| | |
v v v
PostgreSQL file media health and OpenAPI
The backend is a modular monolith. Account, catalog, tasting, journey, progress, media, social, and demo data are separate Java packages with verified Spring Modulith boundaries. This keeps deployment and transactions simple while preserving service boundaries that can be extracted later if scale requires it.
See Architecture for module ownership, request flows, security choices, and the reasons for not starting with microservices.
Stack
| Area | Technology |
|---|---|
| Backend | Java 21, Spring Boot 3.5, Spring Security, Spring Data JPA |
| Architecture | Spring Modulith |
| Database | PostgreSQL 17, Flyway migrations, H2 for fast integration tests |
| Frontend | React 19, TypeScript 5.9, Vite, TanStack Query, React Router |
| Map | D3 Geo, TopoJSON, Natural Earth data through world-atlas |
| Testing | JUnit 5, MockMvc, Testcontainers, Vitest, Testing Library |
| Delivery | Docker Compose, Nginx, GitHub Actions |
Run with Docker
Requirements: Docker Desktop or Docker Engine with Compose.
cp .env.example .env
docker compose up --build
Open:
- Application:
http://localhost:3000 - API documentation:
http://localhost:8080/docs - Backend readiness:
http://localhost:8080/actuator/health/readiness
The local demo account is:
Email: demo@tasteprint.app
Password: tasteprint
Set DEMO_DATA_ENABLED=false before a public deployment. Replace the sample database password in .env with a generated secret.
Run for development
Start the backend. No local database is required because development defaults to a file-backed H2 database in PostgreSQL compatibility mode.
cd backend
.\mvnw.cmd spring-boot:run
In another terminal:
cd frontend
npm install
npm run dev
Vite serves the interface at http://localhost:5173 and proxies API and upload requests to port 8080.
Tests
Backend tests cover authentication, validation, privacy, ownership, tasting CRUD, paging, trip missions, shared challenge progress, media validation, CORS, module boundaries, and application startup.
cd backend
.\mvnw.cmd verify
The PostgreSQL compatibility test runs automatically when Docker is available and skips cleanly when it is not.
Frontend tests cover the API client, expired sessions, protected routing, date behavior, and formatting. The production build also performs strict TypeScript checking.
cd frontend
npm test
npm run lint
npm run build
Configuration
| Variable | Purpose | Local default |
|---|---|---|
DB_URL |
JDBC database URL | File-backed H2 |
DB_USER |
Database user | sa |
DB_PASSWORD |
Database password | Empty |
APP_ALLOWED_ORIGINS |
Comma-separated browser origins | Local frontend ports |
MEDIA_DIRECTORY |
Photo storage path | ./uploads |
DEMO_DATA_ENABLED |
Seed demo accounts and activity | true |
PORT |
Backend HTTP port | 8080 |
Product rules
- A dish counts toward a user's global Tasteprint wherever it was eaten.
- A dish counts toward a trip only when the tasting country and date match that trip.
- A challenge combines member dishes logged within its date range. This allows both travel groups and local tasting clubs.
- Coverage is weighted by cultural importance and means breadth, not expertise.
- Public pages are private by default and never expose email addresses, exact coordinates, restaurant details, or internal timestamps.
- Uploaded files are limited to 6 MB, checked by MIME type and file signature, bound to their owner, and removed with their last tasting or account.
The catalog is intentionally stored as reviewed migration data, not copied blindly from a third-party popularity feed. Adding a destination means adding six balanced cultural entry points with an explanation of why each matters.