Zooterrapia
A complete booking system for a therapeutic farm, from service discovery to secure payment. The Express backend manages a multi-factor availability algorithm with isolated transactions to prevent double-bookings. The Next.js 15 frontend provides a smooth booking flow with Stripe integration. Everything is orchestrated in a Turborepo monorepo with shared types.
Context
An animal-assisted therapy center offers therapeutic sessions with animals for diverse audiences: children, elderly people, and people with disabilities. Appointment management was handled manually, with the associated risks of errors and double bookings. Zooterrapia digitalizes the entire journey, from service discovery to secure payment, while giving administrators a comprehensive dashboard to manage operations.
Architecture
The project is structured as a monorepo with pnpm workspaces and Turborepo, comprising three packages: frontend, backend, and shared.
The Express 5 backend follows a layered architecture (routes → controllers → services → models). 13 Sequelize models define the PostgreSQL schema. The core business logic resides in the booking service, which uses SERIALIZABLE transactions with row-level locking to ensure no time slot is booked twice, even under concurrent load. The payment system relies on Stripe Payment Intents with an idempotent webhook handler for real-time payment confirmation.
The Next.js 15 frontend (React 19, App Router) combines Server Components for public pages with ISR and Client Components for the multi-step booking widget and admin dashboard. Stripe Elements integration provides a secure payment form directly within the booking flow.
The shared package centralizes TypeScript types, Zod validation schemas, and constants, ensuring consistency between frontend and backend at compile time.
Technical challenges
Preventing double bookings. The primary risk in a booking system is double-booking under concurrent load. The solution uses PostgreSQL transactions at SERIALIZABLE isolation level with row-level locking via Sequelize. An exponential backoff retry mechanism handles serialization errors transparently for the user.
Multi-factor availability algorithm. Calculating available slots is not trivial: it requires combining practitioner weekly templates, one-off overrides, blocked dates, and existing appointments for each slot. The availability service resolves this hierarchy in cascade and returns slots with their remaining capacity in real time.
Email automation and tracking. Four cron jobs run in parallel: 24h appointment reminders, expiration of unpaid bookings, post-session feedback requests, and a daily Telegram summary for the administrator. Every send is tracked in an EmailLog model for complete auditability.
Project scale
- Backend: 13 Sequelize models, 15+ migrations, 11 controllers, SERIALIZABLE transactions, Stripe webhooks
- Frontend: Next.js 15 App Router, multi-step booking widget, full admin dashboard, Stripe Elements
- Security: JWT with httpOnly refresh tokens, rate limiting (auth 10/15min, booking 3/min), Zod validation, Helmet
- Infrastructure: Docker multi-stage, Docker Compose 5 services, Caddy reverse proxy with auto SSL, CI/CD GitHub Actions
Current status
The backend and frontend are functional with the complete booking flow, Stripe payment, and admin dashboard. The cron job system and automated emails are in place. Docker Compose deployment with Caddy is configured for production. Next steps are Playwright E2E tests and going live.


