Project
Sitafacil
Scheduling and catalog for service businesses, multi-tenant, in production.
- Year
- 2025
- Role
- Full-stack
- Team
- 1
- Stack
- Next.jsFastifytRPCPostgreSQLResendDocker

01
The problem
A service business with two locations ran scheduling on paper: appointments, dates, names and payments. There was no single place for the flow or for listing services and packages. A cut that only served one shop would not be a product: several businesses had to run on the same platform in real time.
02
Technical decisions
I sat with the scheduling flow and modeled it into my own app. I ran the project alone: requirements, design and each iteration. I used AI tools to validate ideas and cuts, not to skip the model. The schema was multi-tenant from the start.
Multi-tenant from the first model
The first case was two locations of one business. The product had to host several businesses at once, not one install per customer.
Next.js + Fastify + tRPC + PostgreSQL
UI and server stay separate; tRPC keeps a typed contract between them. Postgres holds tenants, the schedule, services and packages. Resend handles email.
Docker and CI/CD on GitHub
Images are built in the pipeline and deployed on my own infra. That flow was the part of the project I cared about most: a push should not mean a manual deploy.
03
Interface
Web. A business publishes services and packages and runs the schedule (appointments, dates, payments) in one place. Several tenants on one install. The hero and gallery use production brand assets (Open Graph, lockup and isotype). No UI screenshots yet.

04
Result
2
locations in the first case
Live
sitafacil.com
The demo is at sitafacil.com. The cut comes from a real flow (paper, two locations), not an invented brief. No tenant or booking count is published here.
05
Learnings
The work that stuck was CI/CD on GitHub: Docker image builds and a repeatable deploy. Also: a single-business case is not modeled as a single tenant if the product is a platform. Gathering the flow (appointments, payments, packages) before writing code avoided a generic CRUD.