Quiet Habits #
Quiet Habits is a small self-hosted habit tracker. It runs as a local Node application, stores data in SQLite, and does not require an account, browser extension, or hosted service.
Requirements #
- Node.js 20 or newer
- npm
Local development #
Install dependencies:
npm install
Start the frontend and API together:
npm run dev
Open http://localhost:5173 during development. Vite serves the React UI and proxies /api requests to the Node server on port 3001.
Production-style local run #
Build the frontend and start the server:
npm run build
npm start
Open http://localhost:3001. The server serves the built frontend and the API from the same origin.
You can change the API port with PORT:
$env:PORT = 3100
npm start
PORT and API_RATE_LIMIT_MAX must be positive integers. The process closes its HTTP listener and SQLite connection on SIGINT and SIGTERM.
Data storage #
The SQLite database is created at data/quiet-habits.db. The entire data/ runtime directory is ignored by Git so personal habit data stays local.
The database contains:
habits: habit identity and creation timehabit_checkins: one completion date per habit
Back up the app by copying the database while the server is stopped. To reset all data, stop the server and remove data/quiet-habits.db plus any data/quiet-habits.db-wal and data/quiet-habits.db-shm files. The next start recreates the schema.
The database uses SQLite user_version migrations. The current schema version is 1; future schema changes should be added as numbered migrations rather than editing existing tables manually.
Habit check-ins use the browser's local calendar date. This avoids UTC-midnight surprises for users whose local timezone differs from the host or container timezone.
API smoke check #
With the server running:
Invoke-RestMethod http://localhost:3001/api/health
Invoke-RestMethod http://localhost:3001/api/habits
API requests are limited to 120 requests per client IP per minute by default. For local testing only, API_RATE_LIMIT_MAX can lower that threshold; do not disable throttling in a deployed instance. JSON request bodies are limited to 8 KB. The shared habit-name limit is defined in src/config.js and is enforced by both the UI and API.
Tests and build #
npm test
npm run build
Optional container deployment #
A Containerfile is included for Podman or Docker-compatible runtimes. The SQLite database should be mounted at /app/data so it survives container replacement.
podman build -t quiet-habits .
podman run --rm -p 3001:3001 -v quiet-habits-data:/app/data quiet-habits
For a rootless Podman systemd deployment, copy quiet-habits.container to ~/.config/containers/systemd/, then run:
systemctl --user daemon-reload
systemctl --user enable --now quiet-habits.service
The quadlet uses a named volume called quiet-habits-data and publishes port 3001.
Before enabling the quadlet, build the image with the exact tag it references:
podman build -t localhost/quiet-habits:latest .
systemctl --user daemon-reload
systemctl --user enable --now quiet-habits.service
The UI uses local system font stacks and has no runtime font-provider dependency.
Project structure #
src/: React UI and browser-safe shared domain/configuration modulesserver/app.js: Express app, middleware, API routes, and static servingserver/index.js: production startup entry pointserver/db.js: SQLite connection, schema, and persistence operationsserver/config.js: validated server configurationserver/lifecycle.js: graceful shutdown behaviorserver/api.test.js: HTTP-level API contract testsserver/config.test.js: configuration validation testsserver/lifecycle.test.js: shutdown lifecycle testsdata/: local SQLite runtime data, ignored by Git