Branch master was renamed to main.

Files
breedr/DATABASE.md

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:

  1. Inserts the dog into dogs table (without sire/dam columns)
  2. Creates entries in parents table 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:

  1. Delete the old database (if upgrading):

    rm data/breedr.db
    
  2. Start the server - it will create the correct schema automatically:

    npm run dev
    
  3. Verify the schema:

    sqlite3 data/breedr.db ".schema dogs"
    

    You should see litter_id but NO sire_id or dam_id columns.

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 migrations
  • server/routes/dogs.js - Handles parent relationships via parents table
  • server/index.js - Initializes database on startup

NO MIGRATIONS! The init file is the source of truth.