Web-first symptothermal fertility tracker for Natural Family Planning (NFP).
- Next.js App Router + TypeScript
- Prisma ORM + SQLite
- Email/password auth with Argon2
- Cookie-based server sessions
- Vitest (unit + integration) and Playwright (e2e)
- Single-user deployment (first registration only)
- Daily observation logging
- Creighton mucus fields:
- stretch score (
0,2,6,8,10) - transparency (
Ccloudy,Kclear) - slipperiness (
Nnon-lubricative,Llubricative)
- stretch score (
- Cervix position
- Bleeding level
- Intercourse flag
- Notes
- Creighton mucus fields:
- Rule-based symptothermal interpretation
- Fertile start: day 6 or first fertile-quality mucus
- Temperature shift: 3-over-6 threshold rule
- Post-ovulatory infertile: later of
Peak + 3andTemp shift + 3 - Uncertain status with reason codes for missing/contradictory observations
- Manual per-day override with required reason
- Dashboard, chart, entries, and settings pages
- Educational-only medical disclaimer acceptance
Copy .env.example to .env for local development.
DATABASE_URL="file:./dev.db"
SESSION_TTL_HOURS="720"For Coolify (or Docker), use:
SQLITE_FILE_PATH="/var/lib/nfp/nfp.db"
SESSION_TTL_HOURS="720"Attach a persistent volume mounted at /var/lib/nfp.
npm install
./node_modules/.bin/prisma migrate dev --name init
npm run devOpen http://localhost:3000.
npm run dev: start local dev server (webpack mode)npm run build: production buildnpm run start: start production servernpm run start:prod: run Prisma migrate deploy, then start servernpm run lint: ESLintnpm test: Vitest unit + integration testsnpm run test:e2e: Playwright testsnpm run seed:account: manually seed/update the single account (requires env vars)npm run seed:data:creighton: manually seed Creighton-only cycle data for existing account
Account seed (required first):
SEED_EMAIL="you@example.com" SEED_PASSWORD="your-password" npm run seed:accountThen seed two full Creighton cycles for that account:
SEED_EMAIL="you@example.com" npm run seed:data:creightonNotes:
- Seeding is manual only. No seed command runs automatically in app startup or runtime.
- Data seed is Creighton-only (
BBTintentionally omitted/null).
POST /api/auth/registerPOST /api/auth/loginPOST /api/auth/logoutGET /api/auth/session
GET /api/cycles/currentGET /api/cyclesPOST /api/cyclesGET /api/cycles/:cycleIdPATCH /api/cycles/:cycleIdGET /api/cycles/:cycleId/daysPUT /api/cycles/:cycleId/days/:datePATCH /api/cycles/:cycleId/days/:date/overridePOST /api/cycles/:cycleId/recompute
GET /api/settingsPATCH /api/settingsGET /api/health
- Deploy with included
Dockerfile. - Add persistent volume mounted to
/var/lib/nfp. - Set
SQLITE_FILE_PATH=/var/lib/nfp/nfp.db. - Set
PORT(container listen port) andHOST_PORT(published host port) if needed. - Keep start command as default (
npm run start:prod).
docker build -t nfp-tracker .
docker run -d \
--name nfp-tracker \
-p "${HOST_PORT:-3000}:${PORT:-3000}" \
-v nfp_data:/var/lib/nfp \
-e PORT="${PORT:-3000}" \
-e SQLITE_FILE_PATH=/var/lib/nfp/nfp.db \
-e SESSION_TTL_HOURS=720 \
nfp-trackerdocker compose up -d --buildIf port 3000 is already in use, run:
HOST_PORT=3001 docker compose up -d --buildThis uses docker-compose.yml with:
- service name:
app - published port:
HOST_PORT->PORT(defaults to3000:3000) - named volume:
nfp_datamounted to/var/lib/nfp - SQLite path:
SQLITE_FILE_PATH=/var/lib/nfp/nfp.db
This app is educational and charting-only. It does not diagnose or treat medical conditions.