
Four products, one data model
At a large real estate agency the data never sits in one place. Properties, owners, developers, clients, requests and deals come from different teams and change at different speeds. The catalogue has to stay current, brokers need their pipeline, managers need their numbers, and leads keep coming in all day.
Over eight months we built one platform for all of it: a public website, a mobile app, an internal CRM and a bridge to the agency's existing system. The team was the core of what later became my studio: me on the front end, a part-time second front-end developer, two backend developers, a designer, a PM and QA. The client and the region stay undisclosed under NDA.
A catalogue people can search and come back to
I built the public site with server rendering, for search engines and a fast first screen. Every filter lives in the URL, so a search can be saved, shared or reopened, and useful combinations get their own landing pages. Several thousand properties each get media, a map, the responsible broker and similar offers, next to complexes, developers, articles and vacancies in several languages.
A saved search turns into a subscription: when a matching property appears, the user gets a push, an email or an SMS.
A mobile app from the same code
I made the iOS and Android app with Nuxt and Capacitor on the same GraphQL contracts as the website. The two never drift apart, and nobody has to maintain a second client from scratch.
It still behaves like a native app: phone confirmation and social sign-in, tokens in the device's secure storage, sessions that restore themselves, the system share sheet, deep links and push. A small separate service sends every shared link to the right place: the screen in the app, the store if the app isn't installed, or the web page.
Nine roles, one permission map
I built the CRM front end from start to finish. It serves nine roles, from brokers and back office to content editors and leadership, and the real question was who can see and do what. I put every rule into one permission map. Routes, menus, buttons and requests all go through it, and the backend checks the same map, so a hidden button is never the only protection. Each role gets exactly the access it needs, and a rule changes in one place instead of twenty.
Behind the roles sits the actual work: properties linked to owners, complexes, developers and agents, bulk import and an approval flow for risky edits; clients, leads and deals with merged duplicates and a full history; tasks, a calendar, invoices, a cash desk with euro and USDT balances side by side, XML exports to listing portals and KPI dashboards for each role.
Moving off the old system without stopping it
The hardest part was the agency's existing system. The business ran on it and leads came in non-stop, so we couldn't switch it off for a night, and we couldn't afford to lose a single lead. I worked out the sync with the backend developers: a separate service takes the old system's webhooks and reads its database changes through Debezium CDC.
That service never writes into anyone else's database. It publishes normalised events, and each domain service applies them on its own side. The old and new systems ran in parallel while the agency moved over one process at a time.
Heavy work off the request
I designed the backend architecture together with the backend developers and helped set up the infrastructure. Anything the user shouldn't wait for goes through RabbitMQ: media processing, sync, subscription checks and notifications. Notifications have one layer that decides who gets what and another that only delivers email, SMS and push, so swapping a provider never touches the rules.
Every client talks to one gateway that federates the domain services into a single GraphQL API. The infrastructure is Terraform on Kubernetes, with one Helm chart per service.
Stack






















