Portfolio / Projects / Shiatsu Booking App

Shiatsu Booking App

Full-stack booking web app for a Shiatsu massage practitioner

Status Deployed on homelab (staging) — VPS launch planned
Year 2026
Backend Node.js, Express, MySQL (mysql2), JWT
Frontend React, Vite
DevOps Docker, Docker Compose, Nginx (reverse proxy)
Node.js Express MySQL (mysql2) JWT React Vite Docker Docker Compose Nginx (reverse proxy)

Overview

A full-stack web application built to manage appointment bookings for a Shiatsu massage practitioner.

The app handles client booking, day/time-slot blocking by the practitioner, and a full admin panel — replacing manual booking via phone or paper.

React/Vite frontend, Node/Express backend and MySQL, containerized with Docker Compose and served behind an Nginx reverse proxy on a self-managed Linux server. Hardened with JWT auth, rate limiting, CORS restrictions, Helmet security headers, input validation and automated encrypted backups.

Features

Client booking form with real-time slot availability (polling every 10s)
One booking per client per day validation, server-side enforced
Admin panel with calendar view, CSV export and booking statistics
Practitioner can block entire days or specific time slots
JWT-protected admin routes with rate-limited login (5 attempts / 15 min)
Responsive layout, containerized with Docker Compose for consistent deploys

Technical Details

Containerization
$ docker compose up -d
✓ frontend Up ✓ backend Up ✓ db Up (healthy)
Three-service Docker Compose setup: React build served by an Nginx container, an Express API container, and MySQL 8 with a healthcheck gating backend startup. Named volumes persist data across restarts.
Security Hardening
$ npm audit
found 0 vulnerabilities
Helmet for HTTP security headers, CORS restricted to the app origin, express-rate-limit on the admin login endpoint, server-side input validation, and a random JWT secret — all fixed after a clean npm audit pass.
Automated Backups
$ mysqldump -u root -p shiatsu | gzip > backup.sql.gz
Backup completado con éxito — rotated, last 14 kept
A Bash script dumps and compresses the database daily via anacron (resilient to a machine that is not always on), with automatic rotation of old backups.

Screenshots

Screenshot 1
Screenshot 2
Screenshot 3
Screenshot 4

Challenges & Solutions

01 Vite generates absolute asset paths (/assets/...) that 404 once the app is served under a sub-path — fixed by setting the correct base in vite.config.js and rebuilding.
02 Hardcoded http://localhost:3000 fetch calls in the frontend broke once served behind a reverse proxy — replaced with relative API paths routed by Nginx.
03 Deleting a booking silently "succeeded" in the UI even on a failed request — fixed by checking response.ok before updating local state, and adding the missing auth header.