- Go 82.2%
- HTML 12.7%
- JavaScript 2.5%
- CSS 1.4%
- Makefile 0.7%
- Other 0.5%
Covers quick start (binary and container), all environment variables, Caddy deployment examples (subdomain, subpath, remote proxy), API usage with curl examples, complete route table, tech stack, security features, and development instructions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| cmd/favoritter | ||
| internal | ||
| web | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
Favoritter
A self-hosted web application for collecting and sharing favorites. Movies, songs, cars, lego sets, flowers, pictures — anything you love.
Built as a single Go binary with SQLite, server-rendered HTML, and HTMX for interactivity. Designed to be easy to deploy for technical users who can share access with friends and family.
Features
- Favorites — Add favorites with a description, optional URL, optional image upload, and tags
- Tags — Autocomplete from existing tags, browse by tag
- Privacy — Each favorite can be public or private, with a configurable default
- User profiles — Public or limited visibility, with avatar, bio, and display name
- Admin panel — User management, tag management, signup request approval, site settings
- Signup modes — Open registration, approval-required, or closed
- Atom feeds — Global, per-user, and per-tag feeds
- Import/Export — JSON and CSV, for data portability
- JSON API — Full REST API under
/api/v1/for third-party clients - OpenGraph — Rich link previews when sharing favorites
- Accessible — Semantic HTML, keyboard navigation, screen reader support,
lang="nb", WCAG 2.2 AA target - Proxy-aware — Supports deployment behind Caddy/nginx on a different machine (WireGuard, Tailscale)
Quick Start
Run directly
# Build
go build -o favoritter ./cmd/favoritter
# Run (creates admin user on first start)
FAVORITTER_ADMIN_USERNAME=admin \
FAVORITTER_ADMIN_PASSWORD=changeme \
./favoritter
Open http://localhost:8080 in your browser.
Run with Podman/Docker
BUILDAH_FORMAT=docker podman build \
--build-arg BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg GIT_REVISION="$(git describe --always --dirty)" \
-t favoritter .
podman run -d \
-p 8080:8080 \
-v favoritter-data:/data \
-e FAVORITTER_ADMIN_USERNAME=admin \
-e FAVORITTER_ADMIN_PASSWORD=changeme \
favoritter
Configuration
All configuration is via environment variables. Sensible defaults are provided for everything except the initial admin credentials.
| Variable | Default | Description |
|---|---|---|
FAVORITTER_ADMIN_USERNAME |
(none) | Initial admin username (created on first run) |
FAVORITTER_ADMIN_PASSWORD |
(none) | Initial admin password |
FAVORITTER_DB_PATH |
./data/favoritter.db |
SQLite database file path |
FAVORITTER_LISTEN |
:8080 |
Listen address (e.g. :8080, 10.0.0.5:8080) |
FAVORITTER_BASE_PATH |
/ |
Base path for subpath deployment (e.g. /faves) |
FAVORITTER_EXTERNAL_URL |
(auto) | Public URL (e.g. https://faves.example.com). Used for feeds, cookies, OpenGraph. If unset, inferred from Host header. |
FAVORITTER_TRUSTED_PROXIES |
127.0.0.1 |
Comma-separated IPs/CIDRs to trust for X-Forwarded-For (e.g. 100.64.0.0/10 for Tailscale) |
FAVORITTER_UPLOAD_DIR |
./data/uploads |
Directory for uploaded images |
FAVORITTER_MAX_UPLOAD_SIZE |
10485760 |
Maximum upload size in bytes (10 MB) |
FAVORITTER_SESSION_LIFETIME |
720h |
Session cookie lifetime (30 days) |
FAVORITTER_ARGON2_MEMORY |
65536 |
Argon2id memory cost in KiB (64 MB) |
FAVORITTER_ARGON2_TIME |
3 |
Argon2id iterations |
FAVORITTER_ARGON2_PARALLELISM |
2 |
Argon2id parallelism |
FAVORITTER_RATE_LIMIT |
60 |
Auth endpoint rate limit (requests/minute/IP) |
FAVORITTER_SITE_NAME |
Favoritter |
Site name shown in UI and feeds |
FAVORITTER_DEV_MODE |
false |
Enable live template reload and debug logging |
Deployment
Behind Caddy (subdomain)
faves.example.com {
reverse_proxy 10.0.0.5:8080
}
Set FAVORITTER_EXTERNAL_URL=https://faves.example.com and FAVORITTER_TRUSTED_PROXIES=<caddy-ip>.
Behind Caddy (subpath)
example.com {
handle_path /faves/* {
reverse_proxy 10.0.0.5:8080
}
}
Set FAVORITTER_BASE_PATH=/faves and FAVORITTER_EXTERNAL_URL=https://example.com/faves.
Remote proxy (WireGuard/Tailscale)
Caddy and Favoritter can run on different machines. Set FAVORITTER_TRUSTED_PROXIES to the proxy's IP or CIDR (e.g. 100.64.0.0/10 for Tailscale, 10.0.0.0/24 for WireGuard).
API
The JSON API lives under /api/v1/. Authenticate by posting to /api/v1/auth/login to get a session cookie, then use it for subsequent requests.
# Login
curl -c cookies.txt -X POST http://localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"changeme"}'
# List your faves
curl -b cookies.txt http://localhost:8080/api/v1/faves
# Create a fave
curl -b cookies.txt -X POST http://localhost:8080/api/v1/faves \
-H 'Content-Type: application/json' \
-d '{"description":"Blade Runner 2049","url":"https://example.com","privacy":"public","tags":["film","sci-fi"]}'
# Export
curl -b cookies.txt http://localhost:8080/api/v1/export/json
See the route list for all available endpoints.
Routes
Web
| Method | Path | Description |
|---|---|---|
| GET | / |
Home page / public feed |
| GET/POST | /login |
Login |
| GET/POST | /signup |
Signup (mode-dependent) |
| POST | /logout |
Logout |
| GET/POST | /reset-password |
Forced password reset |
| GET | /faves |
Own faves list |
| GET | /faves/new |
New fave form |
| POST | /faves |
Create fave |
| GET | /faves/{id} |
View fave |
| GET | /faves/{id}/edit |
Edit fave form |
| POST | /faves/{id} |
Update fave |
| DELETE | /faves/{id} |
Delete fave |
| GET | /tags/search?q= |
Tag autocomplete (HTMX) |
| GET | /tags/{name} |
Browse by tag |
| GET | /u/{username} |
Public profile |
| GET/POST | /settings |
User settings |
| POST | /settings/avatar |
Upload avatar |
| POST | /settings/password |
Change password |
| GET | /export |
Export page |
| GET | /export/json |
Download JSON |
| GET | /export/csv |
Download CSV |
| GET/POST | /import |
Import page |
| GET | /feed.xml |
Global Atom feed |
| GET | /u/{username}/feed.xml |
User Atom feed |
| GET | /tags/{name}/feed.xml |
Tag Atom feed |
| GET | /admin |
Admin dashboard |
| GET/POST | /admin/users |
User management |
| GET | /admin/tags |
Tag management |
| GET/POST | /admin/signup-requests |
Signup request management |
| GET/POST | /admin/settings |
Site settings |
| GET | /health |
Health check |
API (/api/v1/)
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/login |
Login, returns session |
| POST | /api/v1/auth/logout |
Logout |
| GET | /api/v1/faves |
List own faves |
| POST | /api/v1/faves |
Create fave |
| GET | /api/v1/faves/{id} |
Get fave |
| PUT | /api/v1/faves/{id} |
Update fave |
| DELETE | /api/v1/faves/{id} |
Delete fave |
| GET | /api/v1/tags?q= |
Search tags |
| GET | /api/v1/users/{username} |
Public profile |
| GET | /api/v1/users/{username}/faves |
User's public faves |
| GET | /api/v1/export/json |
Export own faves |
| POST | /api/v1/import |
Import faves |
Tech Stack
- Go (1.22+ stdlib router,
html/template,embed,log/slog) - SQLite via modernc.org/sqlite (pure Go, no CGO)
- HTMX for interactive UI without a JS framework
- Pico CSS for semantic HTML styling
- Argon2id for password hashing
- gorilla/feeds for Atom feed generation
Only 3 external Go dependencies.
Security
- Argon2id password hashing with timing-attack prevention
- CSRF double-submit cookie pattern (auto-included by HTMX)
- Security headers: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- Rate limiting on authentication endpoints
- Image uploads: EXIF metadata stripped, re-encoded, UUID filenames, path traversal protection
- Proxy-aware: trusts
X-Forwarded-Foronly from configured proxy CIDRs - Session cookies: HttpOnly, Secure (from
EXTERNAL_URLorX-Forwarded-Proto), SameSite=Lax
Development
# Run in dev mode (live template reload, debug logging)
FAVORITTER_DEV_MODE=true \
FAVORITTER_ADMIN_USERNAME=admin \
FAVORITTER_ADMIN_PASSWORD=dev \
go run ./cmd/favoritter
# Run tests
go test ./...
License
GNU Affero General Public License v3.0 (AGPL-3.0-or-later)