Kittinat Thongmai

← Selected work

ElefyMove: long-stay rental platform for Thailand

A multi-app rental platform that carries a tenancy from booking and payment through a bilingual contract, handover and payout to the landlord.

Period
Jul 2025 – present
Role
Co-founder and lead engineer: backend API and payment system, plus web, mobile and test work.
Live
elefymove.com

Problem

Foreigners moving to Thailand and local renters face scattered listings, worries about deposits, and paperwork that their visas depend on. Listing portals sell leads and stop there.

ElefyMove covers the whole tenancy on one platform: booking, payment, a bilingual contract, check-in and check-out handover, and payout to the landlord.

What I built

  • PayPal and Stripe payment integrations, with payment and payout logic kept inside a single owning module.
  • A transactional outbox with a background dispatcher, so side effects run reliably and are retried after failures.
  • A public Open API with API keys, per-client rate limiting, webhooks, and endpoints for listings, availability and sync.
  • A NestJS API (TypeORM, PostgreSQL, Redis) organised into about 70 feature modules, including bookings, payments, deposit return, disputes, contracts, KYC and invoices.
  • Bilingual rental contracts generated as PDFs, with a clause library, content hashing and Thai line wrapping so Thai text renders correctly.
  • Four Next.js web apps: a renter site in 9 locales (including Thai), a landlord back office, an admin portal, and a developer portal for a public Open API.
  • Two Expo / React Native apps (SDK 54), one for renters and one for landlords. The renter app covers booking, checkout, contracts, KYC, chat, disputes and a map.
  • A containerised local stack with Docker Compose, a Playwright end-to-end suite and k6 load-test scripts.

Engineering highlights

Outbox that scales across replicas

The dispatcher claims ready rows with FOR UPDATE SKIP LOCKED inside a short lease transaction, then does the work outside it. Several API replicas share the work without leader election. If a worker crashes, its lease expires and the rows are delivered again. Rows that keep failing back off exponentially and are parked as dead letters after a set number of attempts.

Ownership boundaries enforced in CI

A fast, syntax-only ESLint config runs as a blocking CI step. It stops other modules from importing payment and payout entities directly, so they must go through the owning module's services. The list of allowed exceptions is a ratchet: it can only shrink.

Money previews match execution

Prices, fees and payouts are itemised before anyone commits, and the preview must match what is executed. The wording never implies escrow or custody, and the UI states that deposits are held by the landlord.

Thai-first text handling

The contract PDF pipeline has dedicated unit tests for text sanitising and Thai line wrapping.

Tests at every layer

About 900 backend spec files, a Playwright suite of 36 spec files covering renter, landlord and admin journeys, and a k6 script whose profile ramps to 10,000 virtual users with latency and failure thresholds (a script target, not a measured production result).

Codebase at a glance

  • 9 locales on the renter web app (de-DE, en-US, es-ES, fr-FR, ja-JP, ko-KR, ru-RU, th-TH, zh-CN)
  • About 70 backend feature modules
  • 7 apps in one workspace: API, renter web, landlord back office, admin portal, developer portal, and 2 mobile apps
  • About 900 backend spec files and 36 Playwright spec files (these count files, not test cases)

Stack

  • TypeScript
  • NestJS 11
  • TypeORM
  • PostgreSQL
  • Redis
  • Next.js 16
  • React 19
  • next-intl
  • Expo / React Native (SDK 54)
  • PayPal
  • Stripe
  • AWS S3
  • Docker Compose
  • GitHub Actions
  • Playwright
  • k6