5.3 KiB
BREEDR Database Schema
Overview
This document describes the clean database schema for BREEDR. NO migrations - fresh installs create the correct schema automatically.
Schema Design
Core Principle: Parents Table Approach
The dogs table does NOT have sire/dam columns. Parent relationships are stored in the separate parents table. This design:
- Keeps the schema clean and normalized
- Allows flexible parent relationships
- Supports future extensions (multiple sires, surrogates, etc.)
Tables
dogs
Core registry for all dogs.
CREATE TABLE dogs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
registration_number TEXT UNIQUE,
breed TEXT NOT NULL,
sex TEXT NOT NULL CHECK(sex IN ('male', 'female')),
birth_date DATE,
color TEXT,
microchip TEXT,
photo_urls TEXT, -- JSON array
notes TEXT,
litter_id INTEGER, -- Links to litters table
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (litter_id) REFERENCES litters(id) ON DELETE SET NULL
);
Important: NO sire_id or dam_id columns!
parents
Stores sire/dam relationships.
CREATE TABLE parents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL, -- The puppy
parent_id INTEGER NOT NULL, -- The parent
parent_type TEXT NOT NULL CHECK(parent_type IN ('sire', 'dam')),
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
FOREIGN KEY (parent_id) REFERENCES dogs(id) ON DELETE CASCADE,
UNIQUE(dog_id, parent_type) -- One sire, one dam per dog
);
litters
Breeding records and litter tracking.
CREATE TABLE litters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sire_id INTEGER NOT NULL,
dam_id INTEGER NOT NULL,
breeding_date DATE NOT NULL,
whelping_date DATE,
puppy_count INTEGER DEFAULT 0,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sire_id) REFERENCES dogs(id) ON DELETE CASCADE,
FOREIGN KEY (dam_id) REFERENCES dogs(id) ON DELETE CASCADE
);
health_records
Health tests, vaccinations, exams, treatments.
CREATE TABLE health_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL,
record_type TEXT NOT NULL CHECK(record_type IN ('test', 'vaccination', 'exam', 'treatment', 'certification')),
test_name TEXT,
test_date DATE NOT NULL,
result TEXT,
document_url TEXT,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
);
heat_cycles
Female heat cycle tracking for breeding timing.
CREATE TABLE heat_cycles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
progesterone_peak_date DATE,
breeding_date DATE,
breeding_successful INTEGER DEFAULT 0,
notes TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE
);
traits
Genetic trait tracking and inheritance.
CREATE TABLE traits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL,
trait_category TEXT NOT NULL,
trait_name TEXT NOT NULL,
trait_value TEXT NOT NULL,
inherited_from INTEGER, -- Parent dog ID
notes TEXT,
FOREIGN KEY (dog_id) REFERENCES dogs(id) ON DELETE CASCADE,
FOREIGN KEY (inherited_from) REFERENCES dogs(id) ON DELETE SET NULL
);
API Usage
Creating a Dog with Parents
POST /api/dogs
{
"name": "Puppy Name",
"breed": "Breed Name",
"sex": "male",
"sire_id": 5, // Parent male dog ID
"dam_id": 8, // Parent female dog ID
"litter_id": 2 // Optional: link to litter
}
The API route automatically:
- Inserts the dog into
dogstable (without sire/dam columns) - Creates entries in
parentstable linking to sire and dam
Querying Parents
-- Get a dog's parents
SELECT p.parent_type, d.*
FROM parents p
JOIN dogs d ON p.parent_id = d.id
WHERE p.dog_id = ?;
-- Get a dog's offspring
SELECT d.*
FROM dogs d
JOIN parents p ON d.id = p.dog_id
WHERE p.parent_id = ?;
Fresh Install
For a fresh install:
-
Delete the old database (if upgrading):
rm data/breedr.db -
Start the server - it will create the correct schema automatically:
npm run dev -
Verify the schema:
sqlite3 data/breedr.db ".schema dogs"You should see
litter_idbut NOsire_idordam_idcolumns.
Troubleshooting
"no such column: weight" or "no such column: sire_id"
Solution: Your database has an old schema. Delete it and let the app recreate it:
rm data/breedr.db
npm run dev
Parent relationships not saving
Check server logs. You should see:
✓ Dog inserted with ID: 123
Adding sire relationship: dog 123 -> sire 5
✓ Sire relationship added
Adding dam relationship: dog 123 -> dam 8
✓ Dam relationship added
If relationships aren't being created, check that sire_id and dam_id are being sent in the API request.
Database Files
server/db/init.js- Creates clean schema, no migrationsserver/routes/dogs.js- Handles parent relationships viaparentstableserver/index.js- Initializes database on startup
NO MIGRATIONS! The init file is the source of truth.