Live app: https://routebook-frontend.onrender.com Live API: https://routebook-da3w.onrender.com/api/drivers
Both hosted on Render's free tier — services spin down after 15 minutes of inactivity, so the first request after idle time may take 30-60 seconds to wake up.
RouteBook is an institutional-knowledge management system for Cintas Service Sales Representative (SSR) routes — a digital replacement for the sticky notes, texts, and tribal knowledge that carry gate codes, dock hazards, parking rules, and access instructions between drivers when a route changes hands.
When an SSR route gets reassigned, the knowledge that made the outgoing driver efficient — "the gate code resets monthly," "don't park in front of the dock doors," "ask for Mike at the front desk" — usually doesn't transfer with it. RouteBook gives that knowledge a permanent, searchable home, attached directly to the route or the specific stop it applies to.
- Backend: Spring Boot 3.5.16, Java 21 (target), Spring Data JPA, Spring Security, Bean Validation
- Database: PostgreSQL in production; H2 (in-memory) for local development and tests
- File storage: Cloudflare R2 (S3-compatible) via AWS SDK v2, for attachment photos/PDFs/documents/videos
- Build: Maven, containerized with Docker for deployment
- CI: GitHub Actions, running the full test suite on every push and PR
Companion frontend: routebook-frontend (React 19 + TypeScript + Vite + Tailwind CSS v4).
- Route/Stop/Driver/Location model — mirrors real SSR route structure
- Knowledge entries with exactly one target — every note attaches to either a Route or a Stop, never both, never neither (enforced at the service layer, with an entity-level backstop)
- Full CRUD — create, edit, and delete for routes, stops, and knowledge entries, with correct cascading cleanup (deleting a route removes its stops and every associated knowledge entry and attachment; deleting a stop or entry cleans up its own attachments, both the R2 object and the database row)
- Atomic writes — creating a stop (which requires a new Location) happens in a single
@Transactionaloperation, so a failure partway through can never leave orphaned data - File attachments — photos, PDFs, Word docs, plain text, spreadsheets, and videos, stored in Cloudflare R2, served via time-limited presigned URLs. Verified end-to-end against a live bucket, including real uploads through the live app.
- Real server-side search — a database-level, case-insensitive query on title/body text, not a fetch-everything-and-filter approach
- Authentication — public read access for browsing; HTTP Basic auth gates every write operation, with a dedicated
/api/auth/verifyendpoint so clients can confirm credentials are correct rather than assume so - Structured error handling — every API error returns a consistent
{status, message, timestamp, fieldErrors}shape - Content-type and size validation — 25MB cap for photos/documents, 250MB for videos, with an explicit content-type whitelist
- SETUP.md — getting the backend running locally, environment variables, testing
- ARCHITECTURE.md — layered design, domain model, key decisions
- API_REFERENCE.md — full endpoint reference with examples
35 tests: unit tests for the Route/Stop XOR business rule and file upload validation (with mocked R2 calls — no network access needed), plus integration tests exercising the full API surface — happy paths, 404s, validation errors, authentication enforcement, and cascade-delete behavior — against a real Spring context and test H2 database. Runs automatically in CI on every push.
./mvnw testDeployed on Render: the backend as a Dockerized web service connected to a managed PostgreSQL instance, the frontend as a separate static site. See SETUP.md for local setup and environment variable configuration.
- Single admin account, no multi-user management — an intentional design choice. This app has one operator (an SSR or a small team), not a multi-tenant user base, so a full user/role system would be over-engineering for the actual use case.
- Split-repo structure (backend and frontend as separate GitHub repos, rather than a monorepo) — a deliberate choice to mirror independent deployment (they're deployed as two separate Render services, versioned and released independently), at the cost of cross-repo documentation links needing full URLs instead of relative paths, and slightly more overhead keeping both repos' CI/docs in sync.
- Free-tier hosting — cold starts after inactivity, and the free Postgres instance has a periodic renewal requirement.
