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.

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. lintcargo fmt --check, leptosfmt --check, cargo clippy -- -D warnings
  2. testcargo test (unit tests)
  3. buildcargo 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