All Projects

In Development

SaaS Platform

DojoBooking

A dual-sided accommodation booking platform with a full financial infrastructure for property owners, complete with KYC verification, TOTP 2FA, wallet management, and real-time data streaming.

Role: Backend Lead / Sole Backend Developer


NestJSTypeScriptMySQLRedisTOTP 2FAEscrowKYCSocket.IOBooking
Problem Statement

Property owners and guests lacked a unified, trustworthy platform for accommodation booking that also handled the full financial lifecycle from payment escrow and guest check-in verification to owner payout management and withdrawal processing in a single, well-structured backend system.

Target users: Two distinct user groups: Guests who want to discover and book accommodations, and Property Owners who need to list, manage, and monetize their properties with a full financial layer including KYC compliance and wallet-based earnings.

Project Walkthrough

DojoBooking started from a real problem: accommodation platforms either handled the booking experience well or the financial layer well — rarely both. Property owners needed more than a listing tool; they needed a full financial infrastructure they could trust. So I set out to build the backend for a dual-sided marketplace that would treat financial operations with the same rigor you would expect from a fintech product.

The architecture is built on NestJS with TypeScript, using a domain-driven module structure that keeps each business concern — authentication, KYC, wallets, payments, communications — cleanly separated. One of my earliest decisions was to use RSA asymmetric keys for JWT signing rather than HMAC, combined with a rotating session token stored in the database. This meant we got stateless auth for most reads, but could still invalidate sessions instantly on new logins without maintaining a token blocklist. Every login rotates the DB token, so old JWTs fail validation even if they have not expired.

The most technically interesting part of this project was the financial infrastructure. The owner wallet system computes balances dynamically from a transaction ledger — summing credits and subtracting debits — rather than maintaining a mutable balance column. This is a deliberate choice: financial data correctness matters more than read performance, and it gives us a complete audit trail. Withdrawal requests run inside TypeORM database transactions, creating a wallet transaction record and a withdrawal record atomically.

On the infrastructure side, I used Bull queues backed by Redis to handle all async side effects — welcome emails, verification links, wallet event notifications — entirely outside the HTTP request cycle. The communication layer is built around a pluggable provider factory pattern where the active email provider is read from a settings table at runtime, enabling live provider switching without a redeployment.


Tech Stack

Languages

TypeScript

Frameworks

NestJS v10 (Express adapter)

Databases

MySQL via TypeORMSQLite for E2E testsRedis for Bull queues

Cloud & Infrastructure

AWS S3Vercel (frontend)

Dev Tools

Swagger/OpenAPIDocker + docker-composeGitLab CIHusky + CommitlintESLint with SonarJSJest unit + E2EPM2TypeORM CLI

Authentication

RSA asymmetric JWT RS256 with rotating session tokens per login; Google OAuth2 via google-auth-library; Email UUID token verification with 10-minute expiry; TOTP 2FA via OTPAuth SHA1 6-digit 30s window with QR code generation

Third-Party APIs & Integrations

Mailgun

Mailtrap

Brevo/Sendinblue

Termii SMS

Google OAuth2 + FCM

AWS S3

Redis


System Architecture

Architecture Pattern

Layered Domain-Driven Monolith with Bull queues for async processing and Socket.IO for real-time events

Request Data Flow

Client -> Middleware (Helmet/Morgan/Compression) -> Global JWT Guard -> Route Guards -> ValidationPipe -> Controller -> Service (TypeORM + Transactions) -> Bull Queue -> Processors -> DB -> Response

Key Engineering Decisions

RSA RS256 JWT with rotating session tokens for stateless-yet-invalidatable auth. Ledger-computed wallet balance for financial accuracy. Atomic 3-record owner registration via TypeORM transaction. Runtime-switchable email/SMS providers via settings table factory pattern. Bull queues for all notification side effects.

Database Design

Key tables: users, owners (with TOTP fields), owner_kycs (S3 doc URLs), owner_profile_checkLists (onboarding flags + last_valid_twofa_time), owner_wallets (DECIMAL 19,4 balances + bank details + restricted flag), owner_wallet_transactions (credit/debit ledger), reservation_payvaults (escrow state machine with boolean status + bigint timestamp columns per lifecycle stage), withdrawals

Module Structure

authusersownersowner-kycsowner-profile-checklistsowner-walletsowner-wallet-transactionswithdrawalsreservation-payvaultsowner-twofascommunications/emailcommunications/smscommunications/push-notificationscommunications/communication-logsfilesdashboardssettingslocationsdata-stream

Key Features & Implementation

TOTP Two-Factor Authentication

Authenticator-app 2FA for owners with full generate/verify/validate/disable lifecycle

How it was built

OTPAuth SHA1 TOTP, window:2 for clock drift tolerance. QR code via qrcode.toDataURL. last_valid_twofa_time tracked in profile checklist.

Owner Wallet & Financial Ledger

Auto-provisioned wallet with payout account management, balance checking, and withdrawal requests

How it was built

Balance computed from transaction ledger not stored column. 3-month payout update rate limit. Atomic withdrawal with DataSource.transaction. Admin restriction capability.

Reservation PayVault (Escrow System)

Payment escrow tracking full booking lifecycle from payment to refund

How it was built

State machine with boolean status + bigint timestamp columns per lifecycle stage. Commission deduction via amount_to_wallet field. RPV-XXXXXXXX auto-generated ref.

Multi-Provider Communication System

Runtime-switchable email (Mailgun/Mailtrap/Brevo) and SMS (Termii) providers

How it was built

Factory pattern with IEmailProvider interface. Active provider read from settings table at runtime. Full communication_logs audit table.

Owner KYC Verification

Document submission workflow with admin review and status notifications

How it was built

S3 document upload via multer. RBAC in controller (Owner submits, Admin reviews). Decision updates profile checklist. Bull queue triggers email notifications on each status change.


Challenges & Engineering Decisions

1) Financial correctness: ledger-computed balance vs mutable column tradeoff. 2) Atomic 3-record owner registration via TypeORM transactions. 3) Session invalidation without blocklist via rotating DB session tokens. 4) Runtime provider factory reading from DB per request for live switching. 5) TOTP clock drift handled with window:

2

Would improve with: Redis cache for wallet balance reads, typed event emitter system, Zod boundary validation.


API Documentation

API Route Namespaces

/v1/auth/user/*/v1/auth/owner/*/v1/auth/google/*/v1/auth/admin/*/v1/users/v1/owners/v1/owner-kycs/v1/owner-twofas/v1/owner-wallets/v1/reservation-payvaults/v1/withdrawals/v1/files/v1/dashboards/user/v1/dashboards/business/v1/settings/v1/locations