Releases: sametcn99/booking-calendar
Release list
Release 1.1.6
Docker Image
docker pull ghcr.io/sametcn99/booking-calendar:1.1.6
Manual release of version 1.1.6.
What's Changed
- Caldav by @sametcn99 in #10
Full Changelog: 1.1.5...1.1.6
Release 1.1.5
Docker Image
docker pull ghcr.io/sametcn99/booking-calendar:1.1.5
Manual release of version 1.1.5.
What's Changed
- Dev by @sametcn99 in #9
New Contributors
- @sametcn99 made their first contribution in #9
Full Changelog: 1.1.4...1.1.5
Release 1.1.4
Docker Image
docker pull ghcr.io/sametcn99/booking-calendar:1.1.4
Manual release of version 1.1.4.
Full Changelog: 1.1.3...1.1.4
Release 1.1.3
Docker Image
docker pull ghcr.io/sametcn99/booking-calendar:1.1.3
Manual release of version 1.1.3.
Full Changelog: 1.1.2...1.1.3
1.1.2
added documentation and landing page
Full Changelog: 1.1.1...1.1.2
1.1.1
fix some mobile ui bugs
Full Changelog: 1.1.0...1.1.1
1.1.0
1.0.0
Booking Calendar (Self-Hosted)
Booking Calendar is a self-hosted PWA designed for single-admin appointment management. It runs on your own server, keeps your data under your control, and supports a complete booking flow using shareable booking links.
This project is specifically optimized for self-hosted deployments:
- API and web app are served from a single process.
- File-based database storage (SQLite/sql.js style) makes backup and migration simple.
- Email notifications are sent using your own SMTP configuration.
- It runs in containers with a straightforward Docker Compose production flow.
Key Features
- Admin panel for slots, appointments, and booking links
- Public booking page for guests
- Double-booking prevention with slot overlap checks
- Email notifications with
.icscalendar attachments - Token-based cancellation links in email
- Public appointment detail pages with persistent shareable links
- Community events with public approval links and approval progress
- Calendar sharing controls, push/email notification toggles, and ICS export (all/range)
- Installable PWA (mobile and desktop)
- IP-based rate limiting
- Asynchronous email sending, so booking responses are not blocked
Tech Stack
- Frontend: React + TypeScript + Base Web, Vite
- Backend: Bun native HTTP server, OOP layers (Controller/Service/Repository)
- Database: TypeORM +
sqljs(file-based persistence) - Email: Nodemailer + Handlebars HTML templates + ICS attachments
Architecture
In production, bun run start serves both backend API and frontend static assets.
- API prefix:
/api/... - Frontend: served from
src/client/dist - Auth: Bearer token
- Persistence: file path configured by
DB_PATH
Quick Start
# 1) Install dependencies
bun install
cd src/client && bun install && cd ../..
# 2) Configure environment
cp .env.example .env
# 3) Edit .env values
# - BASE_URL
# - JWT_SECRET
# - SMTP_*
# - ADMIN_USERNAME / ADMIN_PASSWORD
# - VAPID_* (for push notifications)
# 4) Build frontend
cd src/client && bun run build && cd ../..
# 5) Start app (API + frontend)
bun run startBy default, the app runs on http://localhost:3000.
Docker Quick Start
-
Configure Environment:
cp .env.example .env # Edit .env and set your values (SMTP, ADMIN credentials, etc.) -
Run with Docker Compose:
docker-compose up -d --build
-
Access the App:
Openhttp://localhost:3000.
Note: The database is persisted in the ./data directory. If you change VITE_* variables in .env, you must rebuild the image (docker-compose up -d --build).
If you change VITE_VAPID_PUBLIC_KEY, it is also a VITE_* build-time variable and requires image rebuild.
Development Mode
# Terminal 1: backend (watch)
bun run dev:server
# Terminal 2: frontend (Vite dev)
bun run dev:clientEnvironment Variables
The table below matches .env.example.
| Variable | Description | Example |
|---|---|---|
PORT |
Backend port | 3000 |
HOST |
Backend bind address | 0.0.0.0 |
BASE_URL |
Public base URL (critical for email links) | https://book.example.com |
ADMIN_USERNAME |
Initial admin username | admin |
ADMIN_PASSWORD |
Initial admin password | strong-password |
JWT_SECRET |
JWT signing secret (must be strong and long) | change-me |
SMTP_HOST |
SMTP server hostname | smtp.example.com |
SMTP_PORT |
SMTP port | 587 |
SMTP_USER |
SMTP username | noreply@example.com |
SMTP_PASS |
SMTP password | ... |
SMTP_FROM |
Sender address | Booking <noreply@example.com> |
DB_PATH |
Database file path | ./data/booking.db |
RATE_LIMIT_WINDOW_MS |
Rate-limit window in ms | 60000 |
RATE_LIMIT_MAX_REQUESTS |
Max requests per window | 30 |
VITE_VAPID_PUBLIC_KEY |
Public VAPID key used by browser push subscription (build-time) | BEl... |
VAPID_PRIVATE_KEY |
Private VAPID key used by server to send Web Push | 2Vw... |
VITE_PUBLIC_URL |
Canonical/public app URL used for SEO tags | https://book.example.com |
VITE_SEO_LANG |
HTML lang value |
en |
VITE_SEO_TITLE |
SEO page title | Booking Calendar |
VITE_SEO_DESCRIPTION |
SEO meta description | Personal booking calendar |
VITE_SEO_KEYWORDS |
SEO meta keywords | booking,calendar,appointments,self-hosted |
VITE_SEO_AUTHOR |
SEO author metadata | Booking Calendar |
VITE_SEO_OG_TYPE |
Open Graph type | website |
VITE_SEO_TWITTER_CARD |
Twitter card type | summary |
Notes:
- If
BASE_URLis wrong, cancellation links in emails will be wrong. - Never keep default
JWT_SECRETorADMIN_PASSWORDin production. VITE_VAPID_PUBLIC_KEYis build-time (frontend bundle). Rebuild client/image after changing it.VAPID_PRIVATE_KEYis runtime env var used by the backend push sender.
Web Push (VAPID)
VAPID keys are used for browser push notifications:
VITE_VAPID_PUBLIC_KEY: sent to browser to create push subscription.VAPID_PRIVATE_KEY: stays on server; signs push payload requests.
Generate keys:
bun run generate-vapid-keysThis creates vapid-keys.txt with ready-to-copy .env lines:
VITE_VAPID_PUBLIC_KEY=...
VAPID_PRIVATE_KEY=...Then:
- Copy values into
.env. - Rebuild frontend/client image because
VITE_VAPID_PUBLIC_KEYis embedded at build time. - Restart app/container so backend reads
VAPID_PRIVATE_KEY.
Docker Production Setup Guide
1) Configure .env
- Set
BASE_URLto your public domain. - Verify SMTP credentials and delivery.
- Keep strong values for
ADMIN_PASSWORDandJWT_SECRET.
2) Build and start containers
docker compose up -d --build3) Check runtime health
docker compose ps
docker compose logs -f server4) Update after config changes
docker compose up -d --buildSecurity Checklist
- Change default admin credentials.
- Use a long, random
JWT_SECRET. - Serve publicly only behind HTTPS.
- Restrict firewall rules to required ports (
80/443). - Tighten file permissions for
.envandDB_PATH. - Create regular backups.
Backup and Restore
Because persistence is file-based, backup is straightforward.
- Primary backup target: file configured by
DB_PATH - Recommendation: daily automated backups, copied to separate disk/object storage
Example:
cp ./data/booking.db ./backups/booking-$(date +%F).dbFor restore: stop containers, replace DB file with backup, then start containers again.
Email System
Email bodies are rendered from Handlebars templates under src/server/mail/templates:
booking-confirmation.hbsadmin-notification.hbscancellation-notification.hbs
Template variables are provided in src/server/mail/MailService.ts context objects.
API Endpoints
Interactive API reference is available via Scalar:
- Docs UI:
/docs - OpenAPI spec:
/openapi.json
Troubleshooting
Emails are not sent
- Check all
SMTP_*values. - Confirm SMTP TLS/port requirements.
- Validate
SMTP_FROMformat.
Cancellation link points to wrong domain
- Set
BASE_URLto your production domain.
Frontend loads but API fails
- Verify container status (
docker compose ps). - Inspect server logs (
docker compose logs server).
Usage Note
This repository is designed for personal/self-hosted use. If you need team workflows or multi-tenant support, you should extend auth/roles, auditing, background jobs, and data strategy accordingly.
Full Changelog: https://github.com/sametcn99/booking-calendar/commits/1.0.0