A versioned full-stack reading tracker built to demonstrate modern frontend architecture, API-backed system evolution, deployment hardening, behavioral reliability, and CI-gated development.
Try the deployed full-stack app here:
▶ https://readr-v2-app.vercel.app
The live app runs on a split hosted architecture:
- Frontend: Vercel
- Backend: Render
- Database: Neon PostgreSQL
Current stable feature release:
- v2.4.0 — Engagement & insights expansion
Current milestone:
- v3.0.0 — Deployment, reliability, and release-hardening maturity
Readr is a versioned full-stack reading tracker designed to demonstrate disciplined system growth across frontend architecture, backend persistence, authentication, data ownership, testing, and deployment hardening.
The project began as an offline-first reading log and evolved through staged milestones into a multi-user, API-backed application with:
- a React + TypeScript frontend
- an Express + Prisma backend
- PostgreSQL persistence
- JWT-based authentication
- user-scoped data boundaries
- CI-backed validation
- documented deployment and recovery workflows
A major goal of the project is to show how architecture can evolve safely without sacrificing correctness, maintainability, or release confidence.
The original offline-first app remains available here:
▶ https://github.com/conorgregson/reading-log-app
- Live Demo
- Overview
- Latest Release
- Why This Project
- Key Engineering Highlights
- Authentication & Data Ownership
- Tech Stack
- Architecture
- Deployment & Environment
- Local Development
- Local Workflow Options
- Testing & CI
- Release Process & Operational Docs
- Project Structure
- Screenshots
- Roadmap
- Changelog
- Author
- License
The latest shipped release expands the stable authenticated, API-backed architecture with:
- bulk edit workflows for multi-book mutation
- grouped Undo support for bulk operations
- saved library views with persistent filters and sorts
- dashboard statistics and chart-ready summaries
- reading goals, streaks, and badge progression
- accessibility and regression hardening
v3.0 focuses on infrastructure maturity rather than major product-surface changes.
This milestone improves:
- environment clarity
- local Docker workflow consistency
- health checks and runtime visibility
- CI-backed deploy readiness
- release verification and smoke-test discipline
- documentation, troubleshooting, and recovery guidance
Readr is both a product and a systems-design exercise.
It demonstrates:
- incremental, versioned system evolution
- safe migration from local-first to API-backed persistence
- clear separation of concerns across UI, state, IO, and persistence
- schema-driven validation with Zod and Prisma
- disciplined release engineering with CI and deployment verification
- practical deployment architecture across separate hosting providers
The goal is not just to build features, but to evolve the system intentionally while keeping behavior stable and risks isolated.
The React rebuild prioritized behavioral parity with the earlier offline-first app before API migration. This reduced the risk of mixing architectural change with UI behavior change.
Readr evolved in stages:
- v1.x — offline-first localStorage app
- v2.1 — React + TypeScript rebuild with parity lock
- v2.2 — migration to API-backed persistence
- v2.3 — authentication and strict per-user ownership
- v2.4 — engagement systems and derived read models
- v3.0 — deployment hardening and release maturity
Critical UI behaviors such as sorting, Undo recovery, and filtered-list restoration are designed to remain predictable across state changes.
Authenticated data access is enforced across books, sessions, exports, and imports. Import flows reject invalid relationships and do not trust incoming ownership fields.
Typechecking, linting, tests, build validation, artifact startup checks, health checks, and smoke-test procedures are used to reduce release risk.
Readr uses JWT-based authentication to establish identity and enforce user ownership across protected operations.
Protected areas include:
- books
- sessions
- backup export
- backup import
- account lookup
- authenticated derived data surfaces
Primary endpoints:
POST /api/auth/registerPOST /api/auth/loginGET /api/auth/me
Protected requests use:
Authorization: Bearer <token>This token is issued by the backend during registration or login and is required for all user-scoped operations.
The system is designed to fail safely:
- missing authorization headers are rejected
- malformed bearer tokens are rejected
- invalid signatures are rejected
- expired tokens are rejected
- invalid payload shapes are rejected
- unexpected request keys are rejected by strict validation
- repeated auth write attempts are throttled with structured
429responses
These protections support reliable multi-user isolation and predictable API behavior.
- React 18
- TypeScript (strict mode)
- Vite
- Tailwind CSS
- React Router
- Zustand
- Vitest
- React Testing Library
- Node.js
- TypeScript
- Express
- Prisma ORM
- PostgreSQL
- Zod
- Supertest
- GitHub Actions
- Vercel
- Render
- Neon PostgreSQL
- Docker Compose
- Postman
Browser
-> Vercel frontend
-> Render API
-> Neon PostgreSQL ┌──────────────────────────┐
│ React UI │
│ (Vite + TS + Tailwind) │
└─────────────┬────────────┘
│
▼
Client Services Layer
API-backed persistence (v2.2)
│
▼
┌─────────────────────────────────┐
│ Express API │
│ Node.js + TypeScript + Zod │
└───────────────┬─────────────────┘
│
▼
Business Logic Layer
(services/, controllers/)
│
▼
┌───────────────────────┐
│ Prisma ORM │
│ (Typed DB access) │
└───────────┬───────────┘
│
▼
┌─────────────────────────┐
│ PostgreSQL DB │
└─────────────────────────┘- Frontend
- renders UI
- manages client state
- calls API services
- does not directly access the database
- Backend
- validates requests
- authenticates users
- enforces ownership rules
- exposes API contracts
- manages database access through Prisma
- Database
- persists application state
- enforces relational integrity
- supports secure per-user data storage
Readr uses a split deployment model:
- Frontend: Vercel
- Backend: Render
- Database: Neon PostgreSQL
This separation keeps frontend delivery, backend runtime, and managed database concerns clearly isolated.
Frontend deployment requires:
VITE_API_BASE_URL="https://your-render-service.onrender.com"Important:
- use the backend origin only
- do not include
/api - the client appends
/apiinternally
Backend runtime requires:
DATABASE_URL="postgresql://..."
JWT_SECRET="replace-with-a-secure-secret"
JWT_EXPIRES_IN="7d"
NODE_ENV="production"
PORT="10000"
CORS_ALLOWED_ORIGINS="https://readr-v2-app.vercel.app"
AUTH_RATE_LIMIT_WINDOW_MS="900000"
AUTH_RATE_LIMIT_MAX="10"Repository-level Prisma tooling uses:
DATABASE_URL="postgresql://..."The repository should include:
/.env.exampleclient/.env.exampleserver/.env.exampleserver/.env.test.example
These files should only contain placeholder values, never real secrets.
Readr supports two local backend workflows:
- traditional manual setup
- Docker-based backend + database orchestration
git clone https://github.com/conorgregson/readr-v2.git
cd readr-v2Install dependencies separately for the frontend and backend:
cd client
npm installcd ../server
npm installReadr uses separate environment files for root tooling, frontend local development, and backend runtime.
Create the following files before starting the app.
Create a root .env file for Prisma and repository-level database tooling:
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"This value is used by Prisma configuration and database commands run from the repository. Do not commit real credentials.
Create a .env file inside client/:
VITE_API_BASE_URL="http://localhost:4000"This tells the frontend where to send API requests during local development.
Important:
- Use the backend origin only
- Do not include
/api - The frontend appends
/apiinternally when making requests
Examples:
Correct: http://localhost:4000
Incorrect: http://localhost:4000/api
Create a .env file inside server/:
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
JWT_SECRET="your-development-secret"
JWT_EXPIRES_IN="7d"
NODE_ENV="development"
PORT="4000"
CORS_ALLOWED_ORIGINS="http://localhost:5173,https://readr-v2-app.vercel.app"
AUTH_RATE_LIMIT_WINDOW_MS="900000"
AUTH_RATE_LIMIT_MAX="10"Required backend variables:
DATABASE_URL- PostgreSQL connection string used by the API runtime
JWT_SECRET- Secret used to sign and verify JWTs
JWT_EXPIRES_IN- JWT lifetime configuration
NODE_ENV- Runtime environment name
PORT- Local backend port
CORS_ALLOWED_ORIGINS- Comma-separated list of allowed frontend origins
AUTH_RATE_LIMIT_WINDOW_MS- Rate-limit window for auth write endpoints, in milliseconds
AUTH_RATE_LIMIT_MAX- Maximum register/login attempts allowed within the configured window
Create a .env.test file inside server/ for backend integration tests:
DATABASE_URL="postgresql://username:password@host/test_database"
JWT_SECRET="test-secret"
JWT_EXPIRES_IN="7d"
NODE_ENV="test"
CORS_ALLOWED_ORIGINS="http://localhost:5173"Local
.envfiles do not automatically carry into Vercel or Render. Production configuration must be set separately in each platform.
Use your own local PostgreSQL instance or a hosted development database.
From server:
npx prisma generate
npx prisma migrate dev
npm run devThis generates the Prisma client and applies the latest schema to your local database.
Backend runs at:
http://localhost:4000From client/:
npm run devFrontend typically runs at:
http://localhost:5173Use Docker Compose for local PostgreSQL + backend orchestration.
From the repository root:
docker compose upThis starts:
- local PostgreSQL
- local Express backend
Then start the frontend separately:
cd client
npm run devUseful commands:
docker compose build --no-cache
docker compose updocker compose downdocker compose down -vUse docker compose down -v only if you intentionally want to wipe local containerized database state.
Readr uses layered validation across frontend behavior, backend contracts, and deployment readiness.
Covers areas such as:
- search logic
- Undo behavior
- sorting behavior
- auth state behavior
- token restore flow
- logout state reset
- bulk selection and grouped Undo
- saved view behavior
- dashboard and engagement UI behavior
- accessibility semantics
Tools:
- Vitest
- React Testing Library
- jsdom
Covers areas such as:
- register/login flows
- authenticated account lookup
- protected route enforcement
- invalid and expired token handling
- malformed request handling
- ownership enforcement
- backup import/export integrity
- transaction rollback behavior
- structured hardening responses
Tools:
- Vitest
- Supertest
- PostgreSQL test database
The API is also validated through structured Postman collections for:
- health
- auth
- backup
- books
- sessions
- stats
- engagement
GitHub Actions is used to validate:
- typechecking
- linting
- frontend tests
- backend tests
- production builds
- deploy-readiness checks
- built-artifact startup validation
/healthvalidation- API smoke flows where applicable
This keeps release confidence high and helps catch environment-sensitive failures before deployment.
Readr v3.0 adds lightweight release-process documentation to make deployments safer and more repeatable.
Sprint 4 / Sprint 5 operational docs:
./docs/sprints/v3.0/v3.0-release-checklist.md./docs/sprints/v3.0/v3.0-deployment-verification-checklist.md./docs/sprints/v3.0/v3.0-smoke-test-flow.md
Recommended supporting docs:
These docs separate:
- pre-deploy validation
- post-deploy verification
- rollback and recovery expectations
- deployment architecture reference
- troubleshooting steps for common failures
readr-v2/
│
├── .github/
│ └── workflows/
│ └── ci.yml
│
├── client/
│ ├── scripts/
│ ├── src/
│ │ ├── app/
│ │ ├── features/
│ │ │ ├── auth/
│ │ │ ├── books/
│ │ │ ├── engagement/
│ │ │ ├── sessions/
│ │ │ ├── settings/
│ │ │ └── stats/
│ │ ├── shared/
│ │ │ ├── a11y/
│ │ │ ├── api/
│ │ │ ├── data/
│ │ │ ├── types/
│ │ │ └── ui/
│ │ ├── test/
│ │ └── main.tsx
│ ├── index.html
│ ├── vercel.json
│ ├── vite.config.ts
│ └── vitest.config.ts
│
├── server/
│ ├── postman/
│ ├── prisma/
│ ├── src/
│ │ ├── config/
│ │ ├── db/
│ │ ├── middleware/
│ │ ├── modules/
│ │ │ ├── auth/
│ │ │ ├── backup/
│ │ │ ├── books/
│ │ │ ├── engagement/
│ │ │ ├── saved-views/
│ │ │ ├── sessions/
│ │ │ ├── stats/
│ │ │ └── views/
│ │ ├── tests/
│ │ ├── types/
│ │ ├── utils/
│ │ ├── app.ts
│ │ └── index.ts
│ ├── prisma.config.ts
│ └── tsconfig.json
│
├── docs/
├── shared/
│ └── types/
│ └── v2.4.ts
├── CHANGELOG.md
├── roadmap.md
├── LICENSE.md
└── README.mdA few intentional boundaries shape the repository:
- frontend features are grouped by domain behavior
- shared frontend infrastructure remains centralized
- cross-app contracts live at the repo level
- backend modules map to route-domain responsibilities
- testing exists at multiple layers
- deployment and release docs are versioned alongside implementation work
This structure supports versioned development without collapsing architecture boundaries as the system grows.
The UI below reflects the current full-stack system with API-backed persistence, authentication, user-scoped data, and the v2.4 engagement and insights layer.
All screenshots reflect the live application connected to the production API.
User authentication (login / account access)
Library view with search, filtering, status tracking, and server-backed books data
Saved views with persistent filters, sort state, and active view controls
Search with fuzzy matching and highlight rendering
Bulk selection and grouped library actions
Grouped Undo after bulk status updates
Dashboard with server-derived reading stats and chart-based summaries
Goals, streaks, and badge progression
Session history with deterministic sorting and reading progress tracking
Add Book flow with structured input and validation-ready form
Backup export/import system with ownership-safe data handling
Responsive mobile layout
Readr is developed in versioned milestones where each release isolates a specific risk area before introducing new complexity.
High-level version history:
- v2.0.0 — Backend & CI foundation
- v2.1.0 — React frontend rebuild with full v1.9 behavioral parity
- v2.2.0 — API integration & persistence migration
- v2.3.0 — Authentication and strict user ownership
- v2.4.0 — Engagement & insights expansion
- v3.0.0 — Deployment hardening, release safety, and hosted architecture maturity
For the full roadmap, see roadmap.md.
All notable changes are documented in CHANGELOG.md,
following Keep a Changelog and Semantic Versioning.
Built and maintained by Conor Gregson.
- GitHub: https://github.com/conorgregson
- LinkedIn: https://www.linkedin.com/in/conorgregson
This project is licensed under:
Creative Commons Attribution–NonCommercial 4.0 International (CC BY-NC 4.0)
You may view, use, and modify the source code for non-commercial purposes only. Commercial use requires prior written permission.
Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
See the LICENSE file for details











