Property developer web platform
The public website and content platform for a large residential developer: housing estates and condominiums in three languages, every page editable by the marketing team, and inventory pulled nightly out of a legacy back office that only speaks in files.
- NestJS
- TypeScript
- PostgreSQL
- TypeORM
- Next.js
- React
- AWS S3
- AWS SQS
- CloudFront
- Docker
Overview
A developer's website is a shop window onto inventory that lives somewhere else. The projects, house models, unit plans and prices are maintained in a back-office system built long before the web team existed, and that system exchanges data one way: flat files dropped in a bucket. Meanwhile marketing wants to build landing pages, run campaigns, and change a URL without filing a ticket.
The platform is four applications: a content API and its admin console for staff, and a read API and the public website for visitors — Thai, English and Chinese, with the faceted property search as the main surface.
The parts that were interesting to build
A nightly bridge to a system that only speaks files. Just before midnight local time, a job walks thirteen file types — project details, unit plans, locations, promotions, regions — each with its own mapper that turns platform records into the structure the legacy system expects. The result is serialised, zipped and pushed back to the shared bucket. It is unglamorous work and it is the spine of the whole thing: without it the website and the back office disagree about what is for sale.
Redirects that marketing owns. A site a decade old accumulates thousands of dead URLs, and every one of them is a lost visitor. Redirects are rows in the database, not entries in a config file, and the edge middleware fetches the effective set with a short revalidation window so the table is re-read about once a minute across all requests rather than once per request. Each row is normalised into a comparable host, locale and path before matching, because the same redirect may have been entered as a bare path by one person and a full URL by another. The same middleware resolves the locale and rewrites the faceted search query into its canonical seven-segment path, which is what makes those filter pages indexable.
Analytics writes that do not drown the database. Every public page view records a behaviour event for the personalisation view in the admin. One insert per event would be thousands of writes a minute for data nobody reads in real time, so events buffer in memory and flush on whichever comes first, a size limit or a time limit, with a drain on shutdown so a rolling deploy does not lose the buffer.
Scale
- Four deployables over one database · 61 and 54 entities across the two APIs · 108 migrations
- 397 endpoints · 133 screens · three languages with around 800 strings each
- Thirteen legacy file types synced nightly · about a dozen external integrations
- Status
- Published
- Published
- Sep 12, 2026
- Stack
- NestJS · TypeScript · PostgreSQL · TypeORM · Next.js · React · AWS S3 · AWS SQS · CloudFront · Docker
Keep exploring

