This repository hosts six small, production-grade sample applications that all implement the same simple Patient Management tool. Each sample demonstrates vertical-slice architecture and testable code while using SQLite for persistence and a single OAuth provider for authentication.
- Showcase identical business capabilities implemented across different stacks
- Emphasize vertical slices (feature-first structure) and clear testing boundaries
- Keep the domain intentionally small but realistic
- As a logged-in user, I can create patients
- Patients have the following properties:
id: a 7-digit number starting with a non-zero digit (regex:^[1-9][0-9]{6}$)name: string, maximum 50 characters
- A logged-in user can search for patients by
idorname - A logged-in user may edit a patient’s name
- A logged-in user may archive a patient
- A logged-in user may toggle whether archived patients are included in search
- The search should indicate if an archived patient matches the query, but must not return the archived patient in the list when the toggle is off
- A logged-in user can add a note to a patient
- A logged-in user can see who created a note and when
- A logged-in user can edit and delete notes
- Deleted notes cannot be restored
- Notes have audit histories; history stores previous text
- History cannot be edited, but is deleted with the note
- A logged-in user can search a patient’s notes
- Database: SQLite for all samples
- Authentication: Single OAuth provider across all samples
- Recommended: Auth0 (broad language support, good quickstarts)
- Alternatives: Okta, Azure AD B2C, or GitHub OAuth for simple dev/testing
- Minimum claims: stable user identifier (e.g.,
sub) used to attribute created/updated-by fields
- Validation:
- Patient
idmust match^[1-9][0-9]{6}$ - Patient
namerequired,<= 50characters - Notes
textrequired and non-empty
- Patient
- Archival behavior:
- Archived patients are excluded by default
- When excluded, the search should display a message like “1 archived match hidden” instead of listing the patient
- When included, archived patients appear and are clearly marked
- Patients
patient_id(TEXT or INTEGER),name(TEXT),archived(BOOLEAN/INTEGER), timestamps
- Notes
note_id,patient_id(FK),text,created_by_user_id,created_at,updated_at, soft delete flag or hard delete per stack implementation
- Note History
note_history_id,note_id(FK),previous_text,changed_at,changed_by_user_id
SQLite reference schema (illustrative; each stack may implement via its own migration tooling):
-- Patients
CREATE TABLE patients (
patient_id TEXT PRIMARY KEY CHECK (patient_id GLOB '[1-9][0-9][0-9][0-9][0-9][0-9][0-9]'),
name TEXT NOT NULL CHECK (length(name) <= 50),
archived INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_patients_name ON patients(name);
-- Notes
CREATE TABLE notes (
note_id INTEGER PRIMARY KEY AUTOINCREMENT,
patient_id TEXT NOT NULL REFERENCES patients(patient_id) ON DELETE CASCADE,
text TEXT NOT NULL,
created_by_user_id TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
deleted INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX idx_notes_patient ON notes(patient_id);
CREATE INDEX idx_notes_text ON notes(text);
-- Note History (append-only; removed when note is hard-deleted)
CREATE TABLE note_history (
note_history_id INTEGER PRIMARY KEY AUTOINCREMENT,
note_id INTEGER NOT NULL REFERENCES notes(note_id) ON DELETE CASCADE,
previous_text TEXT NOT NULL,
changed_at TEXT NOT NULL DEFAULT (datetime('now')),
changed_by_user_id TEXT NOT NULL
);- Query by
patient_idor partialname - Toggle: “Include archived” (default: off)
- When toggle is off and matches exist only among archived patients, show a non-intrusive notice like: “N archived matches hidden”
- Notes search is scoped to a specific patient
Each sample organizes code by feature (vertical slices) rather than layers:
- Feature folders:
patients,notes,auth - Each feature includes its own handlers/controllers, validation, data access, and tests
- End-to-end tests cover critical flows: create/search/edit/archive patients; add/edit/delete notes; history behavior; archived toggle behavior
.net + razor pages→dotnet-razor-pages/.net api + vue.js→dotnet-api-vue/.net + blazor→dotnet-blazor/laravel + react→laravel-react/rust + angular→rust-angular/go + react→go-react/
Each sample will provide:
- Local run instructions (SQLite setup, environment variables for OAuth)
- Seed script for demo data
- Migrations (EF Core / Laravel migrations / sqlx/SeaORM/Diesel/etc.)
- Unit, integration, and minimal E2E tests
- Use Auth0 with a single tenant and one Application per sample
- Configure callback/logout URLs per sample’s dev server
- Map the provider
subclaim to the app’screated_by_user_id
- Start by creating the folder for your chosen stack (see names above)
- Implement the schema and stories exactly as specified to preserve comparability
- Keep feature code vertically sliced; keep tests close to features
- Use SQLite and the shared validation rules
This repository is licensed under the terms of the LICENSE file in the root.