Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Health Tracker is a personal health tracking application built with Leptos and Axum.

The problem

Health data is scattered across apps, devices, and spreadsheets. You want a single place to track your metrics, but existing tools are either too complex or too rigid.

The solution

A self-hosted web application that lets you log, visualize, and analyze your health data on your own terms. Built with Rust for performance and reliability, with a responsive Leptos frontend.

How it works

Health Tracker uses SSR + hydration for fast initial loads and smooth client-side interactivity:

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   Browser   │────▶│  Axum Server │────▶│   SQLite    │
│  (Leptos)   │◀────│  (SSR + API) │◀────│  (Storage)  │
└─────────────┘     └──────────────┘     └─────────────┘

Key technologies

ComponentTechnology
FrameworkLeptos 0.8 (full-stack Rust)
ServerAxum
Buildcargo-leptos
StorageSQLite (via sqlx)
StylingSCSS
APIOpenAPI/Swagger (utoipa)
Validationvalidator (derive)

Architecture

Single-crate workspace with feature-gated compilation:

  • src/main.rs — Axum server entry (ssr feature)
  • src/lib.rs — WASM hydration entry (hydrate feature)
  • src/app.rs — Shared components and routes
  • src/api/mod.rs — Leptos #[server] functions for UI-driven data flow
  • src/server/api/ — REST API for external consumers
  • src/shared/ — Framework-agnostic domain types and validation
  • migrations/ — SQLx migration files

Next steps

  1. Quick Start: Get running in 5 minutes
  2. Architecture: Understand the system design
  3. Configuration: Customize settings

Quick start

Get the application running in 5 minutes.

Prerequisites

  • Rust nightly (managed by rust-toolchain.toml)
  • cargo-leptos: cargo install cargo-leptos --locked

1. Clone and build

cargo leptos build

2. Run with hot-reload

cargo leptos watch

Or using just:

just serve

3. Open the app

Navigate to http://127.0.0.1:1337 in your browser.

Development workflow

just          # Show available commands
just dev      # Build in dev mode
just check    # Format + clippy checks
just fix      # Auto-fix formatting
just test     # Run E2E tests
just ci       # Simulate CI pipeline

Next steps

Architecture

Health Tracker uses a single-crate workspace with feature-gated compilation for SSR + hydration.

System overview

┌─────────────────────────────────────────────────────────┐
│                    Health Tracker                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐    ┌──────────────┐    ┌────────────┐ │
│  │   Browser   │◀──▶│  Axum Server │◀──▶│   SQLite   │ │
│  │  (Leptos)   │    │  (SSR + API) │    │  (Storage) │ │
│  └─────────────┘    └──────────────┘    └────────────┘ │
│                                                         │
│  Feature gates:                                         │
│  - `hydrate` → WASM client bundle                       │
│  - `ssr`     → Server binary                            │
│  - `cli`     → Admin CLI binary (stub)                  │
│  - `gen`     → Code generator binary (stub)             │
└─────────────────────────────────────────────────────────┘

Crate structure

src/
├── main.rs              # Axum server entry (ssr feature only)
├── lib.rs               # WASM hydration entry (hydrate feature only)
├── app.rs               # Shared root component + router (both features)
├── api/                 # Leptos #[server] functions (client-server RPC)
│   └── mod.rs           # upsert_daily_log, get_daily_log, get_daily_logs_for_month
├── routes/              # Page components
│   ├── index.rs         # Landing page (/)
│   ├── dashboard.rs     # Dashboard placeholder (/dashboard)
│   ├── track.rs         # Daily log form (/track?date=YYYY-MM-DD)
│   ├── login.rs         # Auth stub (/login)
│   ├── register.rs      # Auth stub (/register)
│   └── docs.rs          # Docs page (/docs)
├── components/          # Reusable UI
│   ├── header.rs        # Navigation bar
│   ├── footer.rs        # Footer with external links
│   ├── daily_log_form.rs # Full daily log form (4 sections)
│   └── layouts/
│       └── main_layout.rs # Header + Outlet + Footer wrapper
├── server/              # SSR-only (#[cfg(feature = "ssr")])
│   ├── db.rs            # AppState with SqlitePool
│   ├── api/
│   │   └── daily_logs.rs # REST CRUD handlers + OpenAPI
│   └── queries/
│       └── daily_logs.rs # sqlx queries + 7 unit tests
├── shared/              # Framework-agnostic domain types
│   ├── models.rs        # DailyLog, DailyLogSummary, ExerciseIntensity
│   └── validation.rs    # DailyLogForm with #[derive(Validate)]
├── styles/
│   └── main.scss        # SCSS styles
└── bin/
    ├── ht-cli.rs        # Admin CLI (cli feature) — stub
    └── ht-gen.rs        # Code generator (gen feature) — stub

Feature gating

FeatureTargetPurposeStatus
hydratecdylib (WASM)Client-side hydrationComplete
ssrbin (native)Server-side rendering + APIComplete
clibin (native)Admin CLI binaryStub
genbin (native)Code generator binaryStub

Both hydrate and ssr share app.rs, routes/, components/, and shared/ for consistency.

SSR + Hydration flow

  1. Server renders the initial HTML via shell() in main.rs
  2. Browser receives HTML + WASM bundle
  3. Hydration takes over in lib.rs, attaching reactivity to existing DOM
  4. Client-side navigation via leptos_router for subsequent pages

Data flow

The app has two parallel data paths:

Path 1: Leptos #[server] functions (UI-driven)

User Action → Form Signal → #[server] function → sqlx query → Database → Response → UI Update

Used by the DailyLogForm component for interactive form submission and loading existing data. Defined in src/api/mod.rs:

  • upsert_daily_log(form) — create or update a log
  • get_daily_log(date) — fetch a single log
  • get_daily_logs_for_month(year, month) — calendar summaries

Path 2: REST API (external/CLI-driven)

HTTP Request → Axum handler → sqlx query → Database → JSON Response

Standard HTTP endpoints for CLI tools and external consumers. Swagger UI at /api/docs. Defined in src/server/api/:

  • GET /api/daily_logs — list all logs (optional ?start_date= and ?end_date=)
  • POST /api/daily_logs — create or update a log
  • GET /api/daily_logs/{date} — get single log by date
  • DELETE /api/daily_logs/{date} — delete log by date

Module dependency rules

routes      → components, api (#[server] functions), shared
components  → shared (never server, never routes, never api)
server      → shared (never components, never routes)
api         → shared, server (queries)
shared      → (nothing)

This ensures clean separation: domain types have no framework dependencies, server code knows nothing about UI, and components are framework-agnostic where possible.

Database

Single SQLite table daily_logs with columns for body metrics (weight, waist), nutrition (protein, calories), wellness (meditation, exercise + intensity), and sleep. Migrations run automatically on server startup via sqlx.

Dependencies

CratePurpose
leptosReactive UI framework
leptos_routerClient-side routing
leptos_metaDocument head management
leptos_axumSSR integration
axumHTTP server
sqlx (sqlite, chrono, runtime-tokio-rustls)Database queries
validator (derive)Input validation
utoipa + utoipa-swagger-uiOpenAPI spec + Swagger UI
tokioAsync runtime
tracingObservability

Configuration

Health Tracker is configured via Cargo.toml under [package.metadata.leptos].

Server settings

SettingDefaultDescription
site-addr127.0.0.1:1337Server bind address
reload-port1338Hot-reload WebSocket port

Build settings

SettingDefaultDescription
site-roottarget/siteOutput directory
site-pkg-dirpkgPackage subdirectory
style-filesrc/styles/main.scssSCSS entry point
assets-dirpublicStatic assets

Feature flags

FeatureDescriptionStatus
ssrServer-side rendering (Axum)Complete
hydrateClient-side hydration (WASM)Complete
cliAdmin CLI binaryStub
genCode generator binaryStub

Environment variables

VariableDescription
DATABASE_URLSQLite connection string (default: sqlite:data/health-tracker.db)
LEPTOS_OUTPUT_NAMEOutput binary name
LEPTOS_SITE_ROOTSite root directory
LEPTOS_SITE_PKG_DIRPackage directory
LEPTOS_SITE_ADDRServer address
LEPTOS_RELOAD_PORTReload port

Lint configuration

Workspace-level clippy::pedantic is set to deny in Cargo.toml. All pedantic lints are warnings during iteration but must be resolved before committing.

Development Setup

Get your local development environment configured.

Prerequisites

  • Rust nightly (managed by rust-toolchain.toml)
  • cargo-leptos: cargo install cargo-leptos --locked
  • lefthook: cargo install lefthook (for pre-commit hooks)
  • commitizen: cargo install cargo-commitizen (for conventional commits)

Setup

# Install dependencies
cargo install cargo-leptos --locked

# Install git hooks
lefthook install

Running

# Development with hot-reload
just serve

# Build for development
just dev

# Build for release
just build

Code quality

# Run format and clippy checks
just check

# Auto-fix issues
just fix

# Run full quality suite
just quality

CI simulation

just ci

This runs the same checks as the Woodpecker CI pipeline locally.

Design System

Color Palette

Based on the Kanagawa color scheme.

Core Tokens

TokenValue
--bg-primarySumi Ink 3
--bg-secondarySumi Ink 2
--bg-elevatedSumi Ink 0
--text-primaryFuji White
--text-secondaryOld White
--accent-primarySpring Blue
--accent-secondaryOni Violet

Theme Variants

  • Default (wave): Dark theme with blue/teal accents
  • Dragon: Warmer dark theme with muted greens
  • Lotus: Light theme with warm paper tones

Set the theme via data-theme attribute: data-theme="dragon" or data-theme="lotus".

Typography

Based on Bringhurst’s typographic scale (ratio 1.25).

Type Scale

TokenSize
--text-xs0.64rem
--text-sm0.8rem
--text-base1rem
--text-lg1.25rem
--text-xl1.563rem
--text-2xl1.953rem
--text-3xl2.441rem
--text-4xl3.052rem

Line Height

TokenValue
--leading-none1
--leading-tight1.25
--leading-normal1.5
--leading-loose2

Spacing

Modular spacing scale matching the type scale:

TokenSize
--space-xs0.5rem
--space-sm0.75rem
--space-md1rem
--space-lg1.25rem
--space-xl1.563rem
--space-2xl1.953rem

Usage

Apply tokens directly in CSS or SCSS:

.card {
  background-color: var(--bg-secondary);
  color: var(--text-primary);
  padding: var(--space-md);
  border: 1px solid var(--border-color);
}

Testing

Health Tracker uses two testing layers: unit tests for database queries and Playwright for end-to-end testing.

Unit tests

Server-side query tests live in src/server/queries/daily_logs.rs and use an in-memory SQLite database. They cover:

  • Upsert (insert and update) operations
  • Date-based retrieval
  • Month summary generation
  • Delete operations

Run with:

cargo test

E2E tests

End-to-end tests live in the end2end/tests/ directory and use Playwright to interact with the running application.

# Run E2E tests
just test

# Run E2E tests in release mode
just test-release

CI integration

Both unit tests and E2E tests run automatically in the Woodpecker CI pipeline. The pipeline order:

  1. lint — cargo fmt --check, leptosfmt --check, cargo clippy -- -D warnings
  2. test — cargo test (unit tests)
  3. build — cargo leptos build --release

Architecture Decision Records

This directory contains Architecture Decision Records (ADRs) for health-tracker.

What is an ADR?

An ADR is a short document that captures an important architectural decision, along with its context and consequences.

Index

0001: Record architecture decisions

Date: 2026-05-18

Status

Accepted

Context

We need to record the architectural decisions made on this project.

Decision

We will use Architecture Decision Records, as described by Michael Nygard.

Consequences

  • Every significant architectural decision should have an ADR
  • ADRs are immutable once accepted (amendments create new ADRs)
  • ADRs live in docs/decisions/ and are versioned with the code