A simple wishlist management application built with Go, HTMX, and SQLite. Features server-side rendering, minimal JavaScript, collapsible family sections, and a clean responsive UI with a fresh mint color scheme.
- 📝 Create and manage wishlist items for family members
- 👥 Add and organize family members with collapsible sections
- ✅ Mark items as reserved/available with pure CSS dropdown (no extra HTTP requests)
- 🔐 Password-protected editing mode
- 📧 Email notifications for all changes
- 🎨 Clean, responsive UI with fresh mint color scheme
- ⚡ HTMX for dynamic updates with minimal page reloads
- 🔒 Session-based authentication
- 📦 Uses templ for type-safe Go templates
- Go 1.25 or higher
- SQLite3 (required for building - the
go-sqlite3driver uses CGO) - Node.js and npm (for CSS linting, optional - see Development Tools section)
golangci-lint(optional, for development - see Development Tools section)
- Clone the repository:
git clone <repository-url>
cd wishpage- Install dependencies:
go mod download- Set up environment variables:
# Required for edit mode
export EDIT_PASSWORD="your-secure-password"
# Optional configuration
export DATABASE_PATH="/path/to/wishlist.db"
export PORT="3002"
# Optional: Email notifications (see Email Notifications section)
export EMAIL_USER="your-email@gmail.com"
export EMAIL_PASS="your-app-password"
export EMAIL_TO="recipient@example.com"- Run the application:
go run .- Open your browser to
http://localhost:3002
| Variable | Description | Default |
|---|---|---|
PORT |
Server port | 3002 |
DATABASE_PATH |
Path to SQLite database | System temp directory (/tmp/wishlist.db on Unix-like systems) |
EDIT_PASSWORD |
Password for edit mode | (required for editing) |
RESET_DB |
Reset database on startup (use with caution!) | 0 |
FORCE_SECURE_COOKIES |
Always use secure cookies (for testing) | Auto-detected based on request protocol |
ALLOW_INSECURE_COOKIES |
Always use insecure cookies (for local dev) | Auto-detected based on request protocol |
EMAIL_HOST |
SMTP server hostname | smtp.gmail.com |
EMAIL_PORT |
SMTP server port | 587 |
EMAIL_USER |
SMTP username | (required for email) |
EMAIL_PASS |
SMTP password | (required for email) |
EMAIL_FROM |
Sender email address | Same as EMAIL_USER |
EMAIL_TO |
Recipient email address | (required for email) |
The application sends automatic email notifications for all wishlist changes:
- Items added, reserved, or deleted
- Family members added or deleted
Important: Email is completely optional. If not configured, the application works normally without sending notifications.
- Enable 2-Factor Authentication on your Google account
- Generate an App Password at https://myaccount.google.com/apppasswords
- Set environment variables:
export EMAIL_USER=your-email@gmail.com
export EMAIL_PASS=your-16-char-app-password
export EMAIL_TO=recipient@example.comOffice 365 / Outlook:
export EMAIL_HOST=smtp.office365.com
export EMAIL_PORT=587
export EMAIL_USER=your-email@outlook.com
export EMAIL_PASS=your-password
export EMAIL_TO=recipient@example.comCustom SMTP Server:
export EMAIL_HOST=smtp.example.com
export EMAIL_PORT=587 # or 465 for TLS
export EMAIL_USER=your-username
export EMAIL_PASS=your-password
export EMAIL_TO=recipient@example.com"Email service verification failed"
- Check SMTP hostname and port are correct
- Verify firewall allows outbound SMTP connections
- Corporate networks may block SMTP ports
"Authentication failed"
- Gmail: Use App Password, not your regular password
- Verify username and password are correct
- Check email provider security settings
Emails not received
- Check spam/junk folder
- Verify
EMAIL_TOaddress is correct - Check server logs for errors
- Some providers have rate limits
The project includes a Makefile with convenient commands for development:
# Validate everything (format, lint, test, tidy) - run this before committing!
make validate
# Show all available commands
make help
# Individual Go commands:
make fmt # Format all Go code with go fmt
make lint # Run golangci-lint with comprehensive checks
make test # Run all tests with race detection and coverage
make tidy # Clean up go.mod and verify dependencies
# CSS commands:
make fmt-css # Format CSS files with Prettier
make fmt-css-check # Check CSS formatting without modifying files
make lint-css # Run Stylelint on CSS files
make lint-css-fix # Run Stylelint and auto-fix CSS issues
# Utility commands:
make clean # Remove build artifacts, caches, and node_modules
make check-deps # Verify all required tools are installed
make npm-install # Install npm dependencies for CSS toolsPrerequisites for development tools:
Go Tools:
golangci-lintis required formake lintandmake validate- Install on macOS:
brew install golangci-lint - Install on Linux:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - See installation guide for other platforms
CSS Tools:
- Node.js and npm are required for CSS linting and formatting
- Install from nodejs.org or via package manager
- Run
npm installormake npm-installto install Stylelint and Prettier - The CI pipeline automatically checks CSS formatting and linting
Recommended workflow:
- Run
make validatebefore committing changes - This ensures code is formatted (Go and CSS), passes linting, and all tests pass
- The Go linter checks 33 different rules for code quality, security, and style
- The CSS linter enforces best practices and prevents common mistakes
CI/CD Pipeline:
- GitHub Actions automatically runs on all pushes and pull requests
- Parallel jobs check Go tests, Go linting, CSS formatting, CSS linting, and build
- All checks must pass before merging
- Coverage reports are uploaded to Codecov
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific test
go test -v -run TestEmailService
# Run tests with coverage (or use 'make test')
go test -v -race -coverprofile=coverage.out ./...This project uses templ for type-safe HTML templates. After modifying .templ files, regenerate the Go code:
# Install templ (if not already installed)
go install github.com/a-h/templ/cmd/templ@latest
# Generate template code
templ generateNote: The generated *_templ.go files are checked into version control. Always regenerate them after editing .templ files and include both the .templ and *_templ.go files in your commits.
wishpage/
├── main.go # Application entry point and server setup
├── auth.go # Authentication and session management
├── database.go # Database initialization
├── handlers.go # Main page handlers
├── items.go # Item CRUD operations
├── family_members.go # Family member CRUD operations
├── email_service.go # Email notification service
├── helpers.go # Utility functions
├── models/
│ └── models.go # Data models
├── templates/
│ ├── layout.templ # Base layout template
│ ├── view.templ # Public view templates
│ ├── edit.templ # Edit mode templates
│ ├── login.templ # Login page template
│ └── error.templ # Error template
├── static/
│ └── css/
│ └── styles.css # Application styles
├── Makefile # Development commands (validate, test, lint, fmt)
├── .golangci.yml # Go linter configuration (33 enabled checks)
├── .stylelintrc.json # CSS linter configuration (Stylelint)
├── .prettierrc.json # CSS formatter configuration (Prettier)
├── package.json # Node.js dependencies for CSS tooling
├── go.mod # Go module dependencies
└── *_test.go # Test files
Page Routes:
GET /- Landing page with collapsible sections for all family members and their itemsGET /login- Login pageGET /static/*- Static files (CSS, JS, images)
API Routes:
GET /api/items- Get all items (supports?family_member_id=Xfilter)PUT /api/items/{id}/reserve- Mark item as reserved, returns updated item card HTML for HTMXGET /api/family_members- Get all family members
Authentication:
POST /api/login- Login with password, returns session cookiePOST /api/logout- Logout and clear session
Page Routes:
GET /edit- Edit mode page (full list of items and family members)
Item Management:
POST /api/items- Create new item (requires name, family_member_id; optional link, price)PUT /api/items/{id}- Update item detailsDELETE /api/items/{id}- Delete itemPUT /api/items/{id}/unreserve- Mark item as unreserved (admin only)
Family Member Management:
POST /api/family_members- Create family memberPUT /api/family_members/{id}- Update family member nameDELETE /api/family_members/{id}- Delete family member (cascades to items)
The application uses HTMX for dynamic updates without full page reloads:
- Item reservation uses pure CSS
<details>dropdown for confirmation (no HTTP request until confirmed) - Reservation confirmation updates just the single item card in-place
- Adding/deleting items updates the list dynamically
- Family member management happens without page navigation
- Collapsible family sections use native HTML
<details>elements - Smooth transitions and feedback
- Secure session tokens with automatic expiration (24 hours)
- Cookie-based authentication (HttpOnly, SameSite=Strict)
- Background cleanup of expired sessions (runs hourly)
- Concurrent access handling with thread-safe session store
- Constant-time password comparison to prevent timing attacks
- SQLite for simplicity and portability
- Foreign key constraints for data integrity
- Cascade deletes for family members (deleting a member removes all their items)
- Automatic schema creation on first run
- Items automatically ordered by price (items without price shown first)
When configured, the application sends HTML-formatted email notifications for all changes:
- Items: added, reserved/unreserved, deleted (includes name, family member, price, link)
- Family Members: added or deleted
The email service supports:
- Both TLS (port 465) and STARTTLS (port 587) connections
- Gmail, Office 365, and custom SMTP servers
- Graceful degradation (app continues if email fails)
- Automatic HTML escaping for security
- Password-protected edit mode with constant-time comparison
- Session-based authentication with automatic expiration (24 hours)
- Smart cookie security - automatically detects HTTPS and sets Secure flag accordingly
- HttpOnly cookies prevent XSS attacks on session tokens
- SameSite=Strict cookies prevent CSRF attacks
- CSRF protection through session validation
- Input validation and sanitization
- SQL injection prevention through parameterized queries
- Email credentials stored in environment variables (never committed)
- HTML escaping in email notifications prevents injection attacks
The application automatically detects whether requests come via HTTPS and sets the Secure cookie flag accordingly:
- ✅ Direct HTTPS: Secure cookies enabled
- ✅ Behind reverse proxy (Cloudflare Tunnel, nginx, Caddy): Detects
X-Forwarded-Proto: httpsheader - ✅ Local HTTP access: Secure cookies disabled (allows LAN access)
- ✅ Mixed environment: Each request is evaluated independently
No configuration needed for typical deployments! The app works seamlessly for:
- Local LAN access over HTTP (
http://192.168.1.100:3002) - Internet access via Cloudflare Tunnel (
https://wishlist.yourdomain.com) - Traditional HTTPS reverse proxies (nginx, Caddy, Traefik)
Override options (rarely needed):
FORCE_SECURE_COOKIES=true- Always use secure cookiesALLOW_INSECURE_COOKIES=true- Never use secure cookies
This application uses Go's embed directive to include all static files (CSS, JS, images) directly in the binary. This means you only need to deploy a single executable file - no separate static directory required!
-
Validate the code before building (recommended):
make validate
-
Build the Docker image:
docker build -t wishpage . -
Run the Docker container:
docker run -d \ -p 3002:3002 \ -e EDIT_PASSWORD=your_super_secret_password \ -e DATABASE_PATH=/data/wishlist.db \ -v $(pwd)/data:/data \ --name wishpage \ wishpage- The
-eflag sets environment variables - The
-vflag mounts a local directory (./data) into the container at/datafor database persistence - Without the volume mount, your data will be lost if the container is removed
- The
-
Access the application:
The application will be accessible at
http://localhost:3002
- Validate the code before building:
make validateThis ensures your code is properly formatted, passes all linter checks, and all tests pass.
- Build the application:
go build -o wishpageThis creates a single, self-contained binary with all static files embedded.
- Copy the binary to your server:
scp wishpage your-server:/opt/wishpage/- Set environment variables on your server:
export EDIT_PASSWORD="your-secure-password"
export DATABASE_PATH="/var/lib/wishpage/wishlist.db"
# Optional: Email configuration
export EMAIL_USER="your-email@gmail.com"
export EMAIL_PASS="your-app-password"
export EMAIL_TO="recipient@example.com"- Run the binary:
./wishpageThat's it! No need to copy the static/ directory - it's already in the binary.
Additional recommendations:
- Consider using a process manager like systemd or supervisor (see example below)
- Put behind a reverse proxy (nginx/Caddy) for HTTPS
Create /etc/systemd/system/wishpage.service:
[Unit]
Description=Wishpage Application
After=network.target
[Service]
Type=simple
User=wishpage
WorkingDirectory=/opt/wishpage
ExecStart=/opt/wishpage/wishpage
Environment="EDIT_PASSWORD=your-password"
Environment="DATABASE_PATH=/var/lib/wishpage/wishlist.db"
Environment="PORT=3002"
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable wishpage
sudo systemctl start wishpage- Default location: If
DATABASE_PATHis not set, the database is created in your system's temp directory (e.g.,/tmp/wishlist.dbon Unix-like systems) - Ensure the database file path is writable
- Check file permissions (the application needs read/write access)
- Verify SQLite3 is installed (required for the go-sqlite3 driver)
- If you need to reset the database, set
RESET_DB=1environment variable (⚠️ this will delete all data!)
- Verify all required variables are set:
EMAIL_USER,EMAIL_PASS,EMAIL_TO - Check server logs for detailed error messages
- Look for "Email service configured correctly" message on startup
- Test with a simple action (add an item) and check spam folder
- Ensure firewall allows outbound SMTP connections
- Verify
EDIT_PASSWORDis set - Clear browser cookies and try again
- Check server logs for session-related errors
Contributions are welcome! Please ensure:
- All validation checks pass:
make validate - Templates are regenerated:
templ generate(if you modified.templfiles) - CSS linting passes if you modify styles:
make lint-css
The make validate command will automatically:
- Format your Go code with
go fmt - Format your CSS with Prettier
- Run comprehensive linting checks (Go and CSS)
- Run all tests with race detection
- Verify dependencies are clean
See the LICENSE file in the root of the repository.