https://github.com/conorgregson/readr-v2
Architecture-driven full-stack system with React, TypeScript, Express, and PostgreSQL. Features authentication, per-user data ownership, and CI-validated API + UI.
https://github.com/conorgregson/readr-v2
authentication ci-cd docker express full-stack nodejs postgresql prisma react rest-api tailwindcss testing typescript vite vitest
Last synced: 3 months ago
JSON representation
Architecture-driven full-stack system with React, TypeScript, Express, and PostgreSQL. Features authentication, per-user data ownership, and CI-validated API + UI.
- Host: GitHub
- URL: https://github.com/conorgregson/readr-v2
- Owner: conorgregson
- License: other
- Created: 2025-12-09T01:02:06.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2026-03-30T01:39:00.000Z (3 months ago)
- Last Synced: 2026-04-02T02:31:15.565Z (3 months ago)
- Topics: authentication, ci-cd, docker, express, full-stack, nodejs, postgresql, prisma, react, rest-api, tailwindcss, testing, typescript, vite, vitest
- Language: TypeScript
- Homepage: https://readr-v2-app.vercel.app
- Size: 1.21 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 40
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.md
- Roadmap: roadmap.md
Awesome Lists containing this project
README
# Readr v2 — Full-Stack Reading Tracker
### _Turn pages into progress._
A versioned full-stack reading tracker built to demonstrate modern frontend architecture, API-backed system evolution, behavioral parity testing, and CI-gated development.


-00C853?style=for-the-badge&logo=github)
---
## Live Demo
Try the deployed full-stack app here:
**▶** https://readr-v2-app.vercel.app
The demo is now connected to the **Express + PostgreSQL backend**, with all reading data persisted via the API.
### Notes
- Data is **fully server-backed (v2.2+)**
- Changes persist across sessions and devices
- **Authentication + user-scoped data now live in v2.3**
- **Backup export/import now live with ownership enforcement**
- **Basic auth endpoint rate limiting now protects repeated register/login attempts**
- **v2.4 is now live**, adding engagement systems, analytics surfaces, and advanced library workflows
> Current stable release: **v2.4.0**
> Next development milestone: **v3.0 — Deployment & Growth**
---
## Overview
**Readr v2** is a full-stack reading tracker designed to demonstrate modern frontend architecture, API-driven persistence, and disciplined system evolution.
It is a structured rewrite of the original offline-first app (**v1.0–v1.9**), transitioning to a scalable, multi-user system.
Key goals:
- Build a **React + TypeScript frontend** with strict behavioral parity guarantees
- Introduce a **typed Express + PostgreSQL backend**
- Enforce **clear separation between UI, state, and persistence**
- Validate correctness through **CI and full-stack testing**
Each version isolates a specific risk area (parity, persistence, ownership) before introducing new complexity.
The original v1.x app remains available here:
**▶** https://github.com/conorgregson/reading-log-app
> Latest official release: **v2.4.0 — Engagement & insights expansion**
> Next development milestone: **v3.0.0 — Deployment & growth**
---
## Latest Release (v2.4)
Readr v2.4 expands the stable authenticated, API-backed architecture with advanced UX and server-derived read models, including:
- bulk edit workflows for multi-book mutation
- grouped Undo support for bulk operations
- saved library views with persistent filters/sorts
- dashboard statistics and chart-ready summaries
- reading goals, streaks, and badge progression
- Sprint 5 hardening for accessibility, recovery, and regression safety
v2.4 is a feature-layering release built on the stable boundaries established in v2.0–v2.3.
---
## Key Engineering Concepts
Readr v2 was designed to demonstrate several real-world frontend engineering patterns:
- **Behavioral Parity Testing**
The React frontend rebuild enforces v1.9 behavioral parity using automated tests to prevent regressions during architectural migration.
- **Deterministic UI State**
Session history sorting is guaranteed deterministic so identical datasets always produce identical ordering.
- **Undo Architecture**
Critical actions (delete / finish) support ~6s undo windows while preserving filters, search state, and list ordering.
- **Local-First → API Migration Strategy**
Readr evolved through a staged migration to reduce system-wide risk:
- v1.x: fully offline-first (localStorage)
- v2.1: React rebuild maintained local persistence for parity lock
- v2.2: full migration to API-backed persistence (Express + PostgreSQL)
This approach ensured UI behavior remained stable while replacing the underlying data layer.
- **CI-Gated Development**
GitHub Actions enforces typecheck, lint, and test validation on every push and pull request.
These patterns mirror practices used in production applications where architectural changes must not introduce behavioral regressions.
---
## Security & Data Ownership (v2.3)
Readr v2.3 introduces strict per-user data boundaries across the system.
Key guarantees:
- All data is scoped to the authenticated user
- Backup export returns only user-owned records
- Backup import enforces ownership (incoming `userId` is ignored)
- Invalid relationships (e.g., orphan sessions) are rejected
- Failed imports rollback completely (no partial writes)
- Auth write endpoints include basic rate limiting on register/login
- Repeated auth attempts return structured `429` responses
These constraints are enforced at both the API layer and database level, and validated through integration testing.
This ensures the system is safe for multi-user environments.
---
## Authentication Flow
Readr v2.3 introduces **JWT-based authentication** to establish identity and enforce strict user ownership across the system.
Core flow:
- Users register or log in through the auth API
- The backend validates credentials and returns:
- a signed JWT
- the authenticated user payload
- The frontend stores the token and restores the session on app load
- Protected API routes require a valid `Authorization: Bearer ` header
- Invalid, expired, malformed, or incorrectly signed tokens are rejected with `401 Unauthorized`
This authentication layer ensures that all protected operations execute within an authenticated user context.
That includes:
- books
- sessions
- backup export
- backup import
- account lookup via `/api/auth/me`
Because identity is established before protected data is accessed, the backend can safely enforce **per-user ownership** on every request.
### Why this matters
Authentication in Readr v2.3 is not just a login feature — it is the foundation for **multi-user data isolation**.
Without authentication, the system could not reliably determine:
- which books belong to which user
- which sessions belong to which library
- which backup payloads are allowed to be imported or exported
By introducing JWT-based auth before expanding multi-user functionality further, v2.3 creates a secure base for future features while preserving the app’s existing behavior and architecture.
---
## Auth API Summary
Primary authentication endpoints:
- `POST /api/auth/register`
- `POST /api/auth/login`
- `GET /api/auth/me`
### Endpoint responsibilities
- `POST /api/auth/register`
- Creates a new user account
- Validates request shape strictly
- Returns a signed JWT and authenticated user payload on success
- `POST /api/auth/login`
- Validates submitted credentials
- Returns a signed JWT and authenticated user payload on success
- `GET /api/auth/me`
- Requires a valid bearer token
- Returns the currently authenticated user
- Rejects missing, malformed, expired, or invalid tokens with `401 Unauthorized`
### Auth contract
Protected requests use the standard authorization header format:
```http
Authorization: Bearer
```
This token is issued by the backend during registration or login and is required for all user-scoped operations.
### Security behavior
The auth layer 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 register/login attempts are throttled with structured `429` responses
Together, these guarantees make authentication predictable at the API boundary and provide a reliable foundation for per-user ownership enforcement across books, sessions, and backup operations.
---
## Parity Summary (v2.1)
**v2.1 goal:** rebuild the v1.9 frontend in **React + TypeScript** with **behavior parity** before any API migration.
**Tier 0 Lock (freeze gates):**
- **Books/Search locked (Sprint 5):** Undo (~6s), highlight parity, autocomplete parity, regression tests
- **Sessions locked (Sprint 7):** CRUD + deterministic sorting, keyboard navigation + live regions, Undo (~6s), highlight parity, regression tests
**Hardening (Sprint 8):**
- Accessibility + focus management baseline
- Corrupt storage resilience
- Performance sanity check on large libraries
**CI baseline (Sprint 9):**
- Typecheck + tests required on PRs
- “Intentional regression” proof test to confirm the suite catches breakages
Canonical docs:
- Parity Charter: [`docs/sprints/v2.1/parity-charter-v2.1.md`](/docs/sprints/v2.1/parity-charter-v2.1.md)
- Architecture: [`docs/sprints/v2.1/architecture-v2.1.md`](/docs/sprints/v2.1/architecture-v2.1.md)
- Test Matrix: [`docs/sprints/v2.1/test-matrix-parity.md`](/docs/sprints/v2.1/test-matrix-parity.md)
- Dependency Map: [`docs/sprints/v2.1/dependency-map-v2.1.md`](/docs/sprints/v2.1/dependency-map-v2.1.md)
---
## Table of Contents
- [Overview](#overview)
- [Key Engineering Concepts](#key-engineering-concepts)
- [Security & Data Ownership (v2.3)](#security--data-ownership-v2.3)
- [Authentication Flow](#authentication-flow)
- [Auth API Summary](#auth-api-summary)
- [Parity Summary (v2.1)](#parity-summary-v2.1)
- [Why This Project](#why-this-project)
- [Roadmap Philosophy](#roadmap-philosophy)
- [Changelog](#changelog)
- [Release Strategy](#release-strategy)
- [Roadmap (High-Level)](#roadmap-high-level)
- [Tech Stack](#tech-stack)
- [Testing & CI](#testing--ci)
- [Project Structure](#project-structure)
- [Architecture](#architecture)
- [Deployment & Environment](#deployment--environment)
- [Engineering Decisions](#engineering-decisions)
- [Screenshots](#screenshots)
- [Installation & Development](#installation--development)
- [Author](#author)
- [License](#license)
---
## Why This Project
Readr is both a product and a systems-design exercise.
It demonstrates:
- Incremental, versioned system evolution
- Safe migration from local-first → API-backed architecture
- Strict separation of concerns across frontend and backend
- Schema-driven validation (Zod + Prisma)
- Full-stack testing (UI parity + API integration)
The goal is not just to build features, but to evolve architecture intentionally while maintaining correctness at every step.
---
## Roadmap Philosophy
Readr is developed in versioned milestones where each release isolates a specific risk area
(e.g., architecture, persistence, UX, or scale) before introducing new complexity.
The roadmap documents not just _what_ was built, but _why_ — serving as both a planning tool
and a technical narrative.
See the full roadmap in [`roadmap.md`](./roadmap.md).
---
## Changelog
All notable changes are documented in [`CHANGELOG.md`](./CHANGELOG.md),
following **Keep a Changelog** and **Semantic Versioning**.
---
## Release Strategy
Readr uses two parallel versioning systems:
### Official Releases (Semantic Versioning)
Major milestones follow **SemVer** and represent stable, coherent deliverables:
- `v2.0.0` — Backend & CI foundation
- `v2.1.0` — React frontend rebuild
- Future versions increment semantically
These releases are published in GitHub Releases.
### Sprint Tags (Development Checkpoints)
During active development, sprint tags are used to mark internal milestones:
- `v2.1-sprint-0`
- `v2.1-sprint-1`
- …
- sprint tags continue throughout active milestone development
Sprint tags serve as:
- Structured iteration checkpoints
- Rollback anchors
- Evidence of disciplined development cadence
Only SemVer releases represent official “ship-ready” states.
---
## Roadmap (High-Level)
- **v2.0.0** — Backend & CI foundation (Express + Prisma + PostgreSQL) ✅
- **v2.1.0** — React frontend rebuild with full v1.9 behavioral parity ✅
- **v2.2.0** — API integration & persistence migration (local-first → API) ✅
- **v2.3.0** — Authentication, accounts, and multi-user data boundaries ✅
- **v2.4.0** — Engagement & insights expansion ✅
- **v3.0.0** — Production infrastructure & hosted deployment architecture
For detailed version history and architectural milestones, see [`roadmap.md`](./roadmap.md).
---
## Tech Stack
### Frontend
- React 18
- TypeScript (strict mode)
- Vite
- Tailwind CSS
- React Router
- Zustand
- Vitest + React Testing Library
### Backend
- Node.js + TypeScript
- Express
- Prisma ORM
- PostgreSQL
- Zod
- GitHub Actions
---
## Testing & CI
Readr includes both **frontend behavioral tests** and **backend integration tests** to validate correctness across the UI, API, and data ownership layers.
This layered strategy helps ensure that architectural changes do not introduce regressions, that protected routes behave predictably, and that user-scoped data boundaries remain enforced.
---
### Frontend Testing (v2.1 → v2.4)
The frontend test suite protects core UI behavior as the app evolves from parity-focused rebuild work into authenticated, API-backed flows.
Covered areas include:
- Search engine logic (tokenization, fuzzy matching, AND semantics)
- Books undo system (delete/restore integrity)
- Sessions sorting (deterministic ordering guarantees)
- Keyboard navigation and accessibility behavior
- Auth store behavior
- Token restore flow
- Logout state reset behavior
- Bulk selection and grouped Undo recovery
- Saved views and library control behavior
- Dashboard recovery and engagement UI behavior
- Accessibility semantics for search/list navigation and progress surfaces
Tools:
- **Vitest**
- **React Testing Library**
- **jsdom**
These tests ensure that the React application remains behaviorally stable while new architecture is introduced underneath it.
---
### Backend Integration Testing (v2.3)
The backend includes **API-level integration tests** to validate correctness, security, and ownership enforcement across the system.
Covered areas:
- **Authentication**
- Register flow
- Login flow
- Authenticated account lookup via `GET /api/auth/me`
- Protected route enforcement (`401` on unauthorized access)
- **Auth hardening**
- Missing authorization headers rejected
- Malformed authorization headers rejected
- Invalid JWTs rejected
- Expired JWTs rejected
- Incorrectly signed tokens rejected
- Invalid token payload shapes rejected
- Unexpected request keys rejected by strict validation
- **HTTP hardening**
- Unknown routes return structured JSON `404`
- Malformed JSON bodies return structured `400`
- Oversized request bodies return structured `413`
- **Ownership enforcement**
- Books are scoped to the authenticated user
- Sessions are scoped to the authenticated user
- Cross-user data access is rejected
- **Backup Export**
- Returns only authenticated user data
- Prevents cross-user data leakage
- **Backup Import**
- Valid payload ingestion
- Duplicate ID rejection
- Orphan relationship validation (sessions → books)
- Transaction rollback on failure
- Forced ownership assignment (never trusts incoming `userId`)
Tools:
- **Vitest**
- **Supertest**
- **PostgreSQL test database (`readr_v2_test`)**
These tests validate that the system enforces **strict per-user data boundaries** and a predictable auth contract, which are core requirements of v2.3.
---
### Postman Test Suites
In addition to automated tests, the API is validated using structured Postman collections:
- `Health/`
- `Auth/`
- `Backup/`
- `Books/`
- `Sessions/`
- `Stats/`
- `Engagement/`
These collections support:
- Manual endpoint verification
- Regression testing during development
- Real-world request/response validation outside the automated test harness
---
### Continuous Integration
GitHub Actions runs automated validation on **every push and pull request**.
Pipeline steps:
1. Type checking
2. ESLint validation
3. Test suite execution (frontend + backend)
This keeps regressions visible early and helps maintain release discipline across sprint branches and merge flow.
---
### Why This Matters
This testing strategy ensures that:
- UI behavior remains stable
- Auth flows remain predictable
- API contracts remain consistent
- Ownership boundaries cannot be bypassed
- Architectural evolution does not come at the cost of correctness
That combination is central to Readr’s development model: **versioned system growth with explicit reliability checks at each stage.**
---
## Project Structure
The repository is organized by **architectural responsibility**, keeping UI, state, API, validation, and persistence concerns clearly separated across the frontend and backend.
```bash
readr-v2/
│
├── .github/
│ └── workflows/
│ └── ci.yml # CI pipeline (typecheck, lint, test)
│
├── client/ # React frontend (Vite + TypeScript)
│ ├── scripts/
│ │ └── gen-backup.mjs # Backup/dev utility script
│ ├── src/
│ │ ├── app/ # App shell, routing, top-level composition
│ │ ├── features/ # Domain features
│ │ │ ├── auth/ # Authentication UI, services, state
│ │ │ │ ├── services/
│ │ │ │ ├── store/
│ │ │ │ └── page.tsx
│ │ │ ├── books/ # Book library flows, bulk actions, saved views
│ │ │ │ ├── components/
│ │ │ │ ├── search/
│ │ │ │ ├── services/
│ │ │ │ ├── store/
│ │ │ │ ├── page.tsx
│ │ │ │ └── types.ts
│ │ │ ├── engagement/ # Goals, streaks, badges, engagement UI/state
│ │ │ │ ├── components/
│ │ │ │ ├── services/
│ │ │ │ ├── store/
│ │ │ │ └── types.ts
│ │ │ ├── sessions/ # Reading session flows
│ │ │ │ ├── components/
│ │ │ │ ├── services/
│ │ │ │ ├── store/
│ │ │ │ ├── page.tsx
│ │ │ │ └── types.ts
│ │ │ ├── settings/ # Import/export and app settings flows
│ │ │ │ ├── services/
│ │ │ │ └── page.tsx
│ │ │ └── stats/ # Dashboard summaries, trends, and stats state
│ │ │ ├── components/
│ │ │ ├── services/
│ │ │ ├── store/
│ │ │ └── page.tsx
│ │ ├── shared/ # Cross-feature building blocks
│ │ │ ├── a11y/ # Accessibility helpers
│ │ │ ├── api/ # API client helpers and shared request logic
│ │ │ ├── data/ # Shared data helpers/constants
│ │ │ ├── types/ # Shared frontend TypeScript types
│ │ │ └── ui/ # Reusable UI primitives/states
│ │ │ └── states/
│ │ ├── test/ # Frontend test setup and helpers
│ │ ├── index.css
│ │ └── main.tsx
│ ├── index.html
│ ├── vercel.json
│ ├── vite.config.ts
│ └── vitest.config.ts
│
├── server/ # Express backend
│ ├── postman/ # Manual API regression collections/reports
│ │ ├── reports/
│ │ ├── Readr-v2-API.postman_collection.json
│ │ └── Readr-v2-local.postman_environment.json
│ ├── prisma/
│ │ ├── migrations/ # Database migration history
│ │ ├── schema.prisma # Prisma schema and model relationships
│ │ └── seed.ts # Seed data script
│ ├── src/
│ │ ├── config/ # Environment/runtime configuration
│ │ ├── db/
│ │ │ └── client.ts # Prisma client setup
│ │ ├── middleware/ # Auth, error handling, request guards
│ │ ├── modules/ # Route-domain backend modules
│ │ │ ├── auth/ # Register, login, me
│ │ │ ├── backup/ # Import/export with ownership enforcement
│ │ │ ├── books/ # Book CRUD
│ │ │ ├── engagement/ # Goals, streaks, badges aggregation
│ │ │ ├── saved-views/ # Persistent saved library views
│ │ │ ├── sessions/ # Session CRUD
│ │ │ ├── stats/ # Dashboard summaries and trends
│ │ │ └── views/ # Shared view mappers / read-model helpers
│ │ ├── tests/
│ │ │ ├── helpers/
│ │ │ ├── integration/ # API integration and hardening tests
│ │ │ ├── setup.ts
│ │ │ └── unit/ # Focused unit tests (including engagement logic)
│ │ ├── types/
│ │ ├── utils/
│ │ ├── app.ts # Express app composition
│ │ └── index.ts # Server entry point
│ ├── prisma.config.ts
│ └── tsconfig.json
│
├── docs/ # Architecture notes, sprint docs, dependency maps, screenshots
├── shared/ # Repo-level cross-app shared contracts
│ └── types/
│ └── v2.4.ts # Shared DTOs/types used by both client and server
├── CHANGELOG.md
├── roadmap.md
├── LICENSE.md
└── README.md
```
### Structure philosophy
A few intentional boundaries shape the repository:
- **Frontend features are domain-oriented**
- Authentication, books, sessions, stats, engagement, and settings are grouped by behavior rather than by file type alone
- **Shared frontend infrastructure stays centralized**
- Accessibility, API helpers, shared UI, data helpers, and common types live under `client/src/shared/`
- **Cross-app contracts live at the repo level**
- Shared DTOs used by both the client and server live under `shared/types/` to reduce contract drift
- **Backend modules map directly to API and derived-read responsibilities**
- Auth, backup, books, saved views, stats, engagement, and sessions are isolated into focused route-domain modules
- **Testing exists at multiple layers**
- Frontend tests protect behavioral parity and UI correctness
- Backend integration tests protect API contracts, auth correctness, and ownership enforcement
- Backend unit tests cover focused logic such as engagement evaluation and derived-state helpers
- Postman collections support structured manual regression checks
This structure supports Readr’s versioned development model: preserving clear architectural boundaries while the system evolves from parity-focused frontend work into secure, multi-user full-stack behavior.
---
## Architecture
### ASCII Diagram
```txt
┌──────────────────────────┐
│ 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 │
│ │
└─────────────────────────┘
```
---
## Deployment & Environment
Readr v2 uses a split hosted architecture:
- **Frontend:** Vercel
- **Backend:** Render
- **Database:** Neon (hosted PostgreSQL)
In local development, the frontend, backend, and database configuration are managed separately so the system can run predictably across environments.
---
### Hosted Architecture
Production request flow:
```txt
browser -> Vercel frontend -> Render API -> Neon database
```
This separation means frontend and backend environment variables are not interchangeable:
- **Vercel** provides the frontend build-time API base URL
- **Render** provides the backend runtime configuration
- **Neon / local PostgreSQL** provides the database connection used by Prisma and the API
---
### Environment Files
Readr uses separate environment files for different responsibilities:
#### Root (`/.env`)
Used by **root-level Prisma tooling**.
Required variable:
```env
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
```
This is used by repository-level Prisma configuration and database tooling.
---
#### Frontend (`client/.env`)
Used by the **Vite frontend** during local development.
Required variable:
```env
VITE_API_BASE_URL="http://localhost:4000"
```
Important:
This value should be the **backend origin only**
Do **not** include `/api`
The frontend appends `/api` internally when making requests
Example:
Correct: `http://localhost:4000`
Incorrect: `http://localhost:4000/api`
---
#### Backend (`server/.env`)
Used by the **Render-hosted Express API** and local backend runtime.
Required variables:
```env
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
JWT_SECRET="replace-with-a-secure-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"
```
---
#### Backend test environment (`server/.env.test`)
Used by backend integration tests.
Example:
```env
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"
```
---
### Example Environment Templates
To document setup safely, the repository should include:
- `/.env.example`
- `client/.env.example`
- `server/.env.example`
- `server/.env.test.example`
These files should contain **placeholder values only**, never real secrets or live connection strings.
Example templates:
`/.env.example`:
```env
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
```
`client/.env.example`:
```env
VITE_API_BASE_URL="http://localhost:4000"
```
`server/.env.example`:
```env
DATABASE_URL="postgresql://username:password@host/database?sslmode=require"
JWT_SECRET="replace-with-a-secure-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"
```
`server/.env.test.example`:
```env
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"
```
---
### Production Environment Responsibilities
**Vercel**
The frontend currently requires:
- `VITE_API_BASE_URL`
This should point to the deployed Render backend origin, for example:
```env
VITE_API_BASE_URL="https://your-render-service.onrender.com"
```
It should **not** include `/api`.
> Some Neon-generated environment variables may still appear in Vercel even when they are not actively used by the frontend, because they were provisioned through a connected integration and cannot be removed from the standard environment variable screen without disconnecting that integration first.
**Render**
The backend runtime requires:
- `DATABASE_URL`
- `JWT_SECRET`
- `JWT_EXPIRES_IN`
- `NODE_ENV`
- `PORT`
- `CORS_ALLOWED_ORIGINS`
- `AUTH_RATE_LIMIT_WINDOW_MS`
- `AUTH_RATE_LIMIT_MAX`
These values must be configured directly in Render and do not inherit from local `.env` files.
---
### Sprint 1 Environment Audit Findings
Sprint 1 identified several environment and deployment hardening points:
- The frontend uses `VITE_API_BASE_URL` as its API base variable.
- Older names such as `VITE_API_BASE` are stale and should not be used.
- The frontend base URL must be the backend origin only, not an `/api` path, because the client appends `/api` internally.
- The backend runtime is centered on `DATABASE_URL`, `JWT_SECRET`, `JWT_EXPIRES_IN`, `NODE_ENV`, `PORT`, `CORS_ALLOWED_ORIGINS`, and auth rate-limit settings.
- Root Prisma tooling uses `DATABASE_URL` separately from frontend deployment configuration.
- No `.env.example` files existed previously, so Sprint 1 included creating them from scratch.
- Auth rate-limit settings are now validated through centralized backend env parsing rather than being handled separately at runtime.
### Deployment environment baseline
- **Render** is the active backend runtime environment and holds the backend-required variables.
- **Vercel** provides the frontend build-time API base URL through `VITE_API_BASE_URL`.
- Local client environment configuration was previously out of sync with actual client code expectations and was corrected during Sprint 1 hardening.
- Root Prisma tooling continues to use `DATABASE_URL` separately from frontend deployment configuration.
### Vercel managed environment variables
Several Neon/Postgres variables are still present in Vercel even though they do not appear to be used by the frontend application code.
These variables are **integration-managed** rather than manually created. Because they were provisioned through a connected Neon integration, they cannot be removed directly from the standard Environment Variables screen. Removing them would require disconnecting the project from the connected integration first.
For now, these variables are being treated as **non-blocking configuration residue** rather than active frontend requirements. The frontend’s relevant deployment variable remains `VITE_API_BASE_URL`, while backend and database runtime configuration are handled separately through Render and Prisma-backed tooling.
### Localhost audit result
Sprint 1 found several `localhost` references across local tooling and development assets. These references are not automatically deployment problems.
Confirmed acceptable local-only references include:
- local Postman environment configuration
- development and test tooling
- local CORS defaults in server env parsing
- client-side dev fallback behavior when running outside production
No evidence has been found so far that production runtime behavior depends on localhost. The more important configuration gap identified during this pass was that auth rate-limit environment variables were being used at runtime without centralized env validation, which has now been corrected as part of Sprint 1 hardening.
---
### Why This Matters
Readr’s hosted architecture depends on clear separation of responsibilities across environments:
- frontend configuration must point to the correct backend origin
- backend runtime must expose the correct secrets, CORS settings, and rate-limit configuration
- Prisma tooling and backend runtime must each have valid database access where required
- local `.env` files do not automatically carry into Vercel or Render
- deployment platforms may also contain integration-managed variables that are not part of the app’s active runtime contract
These boundaries matter because many deployment failures come from configuration drift, naming mismatches, undocumented assumptions, or environment-specific behavior rather than from application logic itself.
Sprint 1 reduces that fragility by standardizing environment templates, correcting client API base configuration, centralizing backend env validation more fully, and documenting the current Vercel + Render + Neon deployment model more clearly.
This makes the hosted stack easier to debug, easier to maintain, and easier to reason about as v3.0 continues.
---
### Mermaid Diagram
```mermaid
flowchart TD
A["React Frontend (Vite + TypeScript + Tailwind)"]
--> B["Client Services Layer (API-backed)"]
B --> C["Express Server (Node + TypeScript)"]
C --> D["Controller Layer"]
D --> E["Service Layer"]
E --> F["Prisma ORM"]
F --> G[("PostgreSQL Database")]
classDef teal fill:#008080,stroke:#004d4d,color:white;
classDef navy fill:#003366,stroke:#001933,color:white;
class A,B teal
class C,D,E navy
class F,G teal
```
---
## Engineering Decisions
Readr v2 emphasizes architectural clarity and incremental evolution over rapid feature expansion.
Key decisions:
### 1. Backend-First Foundation (v2.0.0)
The backend was built and stabilized before rewriting the frontend to:
- De-risk persistence and schema design early
- Lock API boundaries before UI coupling
- Establish CI-backed integration testing from the start
### 2. Parity Before Expansion (v2.1)
The React frontend rebuild prioritizes feature parity with v1.9 before introducing API-backed persistence.
This avoids mixing behavioral changes with architectural migration.
### 3. Local-First → API Migration Strategy
- v1.x: fully offline-first
- v2.1: React rebuild stays local-first (parity lock)
- v2.2: migrate persistence to API (stable UI)
This staged migration reduces system-wide risk and simplifies debugging.
### 4. Strict Separation of Concerns
- UI components are isolated from state logic.
- Stores isolate state from persistence.
- Services abstract IO (local now, API later).
- Backend separates controllers, services, and schemas.
This keeps React → API integration friction low.
### 5. CI as a First-Class Concern
Backend endpoints are validated via automated API tests.
v2.1 expands regression protection with parity tests and CI gating.
Engineering choices are documented to emphasize maintainability and long-term scalability.
### 6. Contract-First Feature Expansion (v2.4)
v2.4 begins with a contract-first sprint that defines request/response shapes, derived-state ownership rules, and feature dependencies before implementation expands into UI and mutation behavior.
This approach is used to reduce the risk of:
- inconsistent DTOs
- duplicated business logic
- premature UI coupling
- boundary erosion across frontend and backend layers
By locking these assumptions early, later sprint work can build on stable contracts instead of inventing them during implementation.
---
## Screenshots
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**

---
## Installation & Development
### 1. Clone the repository
```bash
git clone https://github.com/conorgregson/readr-v2.git
cd readr-v2
```
---
### 2. Install dependencies
Install dependencies separately for the frontend and backend:
```bash
cd client
npm install
```
```bash
cd ../server
npm install
```
### 3. Configure environment variables
Readr uses separate environment files for **root tooling**, **frontend local development**, and **backend runtime**.
Create the following files before starting the app.
#### Root (`/.env`)
Create a root `.env` file for Prisma and repository-level database tooling:
```env
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.
#### Frontend (`client/.env`)
Create a `.env` file inside client/:
```env
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 `/api` internally when making requests
Examples:
Correct: `http://localhost:4000`
Incorrect: `http://localhost:4000/api`
#### Backend (`server/.env`)
Create a `.env` file inside `server/`:
```env
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
- `POST`
- 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
#### Backend test environment (`server/.env.test`)
Create a `.env.test` file inside `server/` for backend integration tests:
```env
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 `.env` files do not automatically carry into Vercel or Render. Production environment variables must be configured separately in each deployment platform.
### 4. Ensure PostgreSQL is available
Readr requires a PostgreSQL database for local backend development.
You can use either:
- a local PostgreSQL installation
- a hosted development database such as Neon
Make sure your DATABASE_URL points to a valid database before running
### 5. Apply database schema
After the database is running, initialize Prisma:
```bash
cd server
npx prisma generate
npx prisma migrate dev
```
This generates the Prisma client and applies the latest schema to your local database.
### 6. Start the backend
From `server/`:
```bash
npm run dev
```
The API will run locally at:
```bash
http://localhost:4000
```
### 7. Start the frontend
From `client/`:
```bash
npm run dev
```
The frontend will run locally through Vite, typically at:
```bash
http://localhost:5173
```
### 8. Run Tests
#### Frontend tests
From `client/`:
```bash
npm run test
```
#### Backend tests
From `server/`:
```bash
npm run test
```
If backend integration tests use a dedicated test database, ensure it is configured and available before running the suite.
### 9. Production deployment
Readr v2 uses a split deployment model:
- **Frontend**: Vercel
- **Backend**: Render
- **Database**: Neon (production) / PostgreSQL for local development
Production environments must provide valid runtime configuration for:
- `VITE_API_BASE_URL`
- `DATABASE_URL`
- `JWT_SECRET`
- `AUTH_RATE_LIMIT_WINDOW_MS`
- `AUTH_RATE_LIMIT_MAX`
### Development Notes
- The frontend depends on the backend being available at the configured API base URL
- Authentication and all protected data flows require a valid backend `JWT_SECRET`
- Register/login endpoints are protected by basic rate limiting in v2.3
- Books, sessions, and backup operations are user-scoped in v2.3
- Local development requires a reachable PostgreSQL database via `DATABASE_URL`
- Local `.env` files do not automatically carry into deployed environments
---
## Author
Built and maintained by **Conor Gregson**.
- **GitHub**: https://github.com/conorgregson
- **LinkedIn**: https://www.linkedin.com/in/conorgregson
---
## License
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](./LICENSE.md) file for details