A private shared calendar for two people. Tracks events by person and category across year, month, and week views, with each person's national public holidays pulled in automatically. Installable as a PWA on mobile.
- Three views: year overview, month grid, week detail
- Events belong to person 1, person 2, or both
- Color-coded categories (Work, Training, Holiday, Travel, Personal, Health — all editable)
- Each person picks their own country in Settings; public holidays for both are fetched from Nager.Date and cached locally
- Single shared passcode — no accounts, no email
- Installable as a home-screen app on iOS and Android
- Mobile-responsive down to iPhone 13 mini (375 px)
| Layer | Technology |
|---|---|
| Server | Node.js + Express |
| Database | SQLite via better-sqlite3 |
| Client | React 18 + Vite |
| Auth | Cookie-based sessions, scrypt password hashing |
| PWA | vite-plugin-pwa + Workbox |
Requirements: Node 18+
# 1. Install server dependencies
npm install
# 2. Install client dependencies
cd client && npm install && cd ..
# 3. Copy env file and edit if needed
cp .env.example .env
# 4. Start the server (watches for changes)
npm run dev
# 5. In a second terminal, start the client dev server
cd client && npm run devThe client dev server proxies /api to localhost:3001, so both run independently.
Open http://localhost:5173. On first load you'll see the setup screen — set a passcode (at least 8 characters) and names.
cd client && npm run build && cd ..
NODE_ENV=production npm startThe server serves client/dist/ as static files and handles all /api routes. One process, one port.
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
Port the server listens on |
HOST |
127.0.0.1 in production, else 0.0.0.0 |
Interface to bind. Loopback-only in production so login rate limiting can't be bypassed by talking to Node directly behind the reverse proxy |
NODE_ENV |
development |
Set to production for secure cookies and static serving |
DB_PATH |
./data/calendar.db |
Path to the SQLite database file |
While logged in: open Settings (gear icon) → "Change Passcode" section at the bottom.
If locked out: run this on the server, then open the app — the setup screen will appear:
sqlite3 data/calendar.db "DELETE FROM settings WHERE key = 'passcode_hash';"