Backend & Data
Hand-written T-SQL across a six-database SQL Server estate at work, including deploy scripts and SAP outbound queues; Python, FastAPI and SQLite in my own apps, built with AI agents.
- SQL Server
- T-SQL
- C# / .NET
// status: open to roles
Software Engineer — backend, integrations, workflows & AI-directed delivery.
I build the unglamorous systems other people depend on: integration layers, databases that have to stay correct across time zones, and dashboards that factory operators use every shift.
Hand-written T-SQL across a six-database SQL Server estate at work, including deploy scripts and SAP outbound queues; Python, FastAPI and SQLite in my own apps, built with AI agents.
I design and build production workflows on a .NET (Orchard Core) workflow engine: SQL Server tasks, JSON definitions, Liquid templates. I also built and maintain the SAP ERP ⇄ MES integration layer that keeps six plants in sync.
An Ubuntu home server: systemd services, Docker Compose stacks, public apps through Cloudflare Tunnel with no open ports, private ones on Tailscale, nightly backups to a second disk.
I run AI coding agents like a small team: a written brief per job, one branch each, and a full test suite that has to pass before anything merges.
Side projects, all of them running rather than half-finished. The private ones hold personal data, so they are described, not linked.
Illustration — the real app holds private data.
// featured
Singapore credit-card optimiser: statement ingest, categorisation, and an assistant that can actually change things.
Singapore reward cards have overlapping bonus categories and monthly spend caps, and the only honest way to know which card to use is to look at what you actually spent.
Private · self-hosted
The problem. Singapore reward cards have overlapping bonus categories and monthly spend caps, and the only honest way to know which card to use is to look at what you actually spent.
What I built. A FastAPI and SQLite app that ingests bank and card statement exports — including scanned PDFs, via Tesseract OCR — categorises transactions, tracks spend caps and bonus thresholds, runs budgets and reports, and recommends which cards to keep, cancel or apply for against a curated catalogue. Telegram and Discord bots for on-the-go queries. On top of that, an LLM assistant with tool use over the app's own functions: it reads and writes through the same code paths the UI uses, confirms before every write, supports undo, and keeps an editable memory file of the owner's rulings. It runs against either an API key or a signed-in Claude Code CLI.
Where it stands. In daily personal use. Self-hosted and private — it sits behind Cloudflare Access with an allow-list of one.
A private family media app: our own video and music library in one place, on phone, desktop and TV, offline included.
The problem. Family videos and music end up scattered across phones, old drives and apps that each work differently. I wanted one private place for our library that keeps working on a plane, and that family members who are not technical can use without asking me.
What I built. A TypeScript monorepo — a Fastify 5 API and a background worker, a React 19 installable web app, SQLite with 80+ forward-only migrations — in front of a Jellyfin video server and a Navidrome music server, all in Docker Compose. Each person has their own library, playlists and storage quota, and a file several people keep is counted once and split between them. Videos remember where each person stopped and show chapters; anything can be saved to the phone for offline use, with a service worker and resumable offline saves. Notifications are web push, and their action buttons still work away from home through a Cloudflare Worker relay that holds HMAC-signed decisions until the server pulls them (sized to stay inside the free tier). An Ops page shows every moving part as green, amber or red with a one-line fix, and admins get a weekly summary.
Where it stands. In use by my family since October 2026, reachable only over Tailscale, never the public internet. Built in a few weeks of evenings with AI coding agents (see How I build): 500+ commits, a log of 100 architecture decisions with rollback notes, and about 3,700 unit tests plus Playwright browser tests at phone and desktop sizes that every deploy must pass.
A self-hosted AI-news dashboard that reads ~37 feeds so I do not have to.
The problem. AI news is spread across vendor blogs, arXiv, Hacker News, Reddit and a dozen outlets that all cover the same story within an hour of each other.
What I built. A Flask server that keeps ~37 feeds pre-fetched in a background loop and persists the cache to disk, so the page opens instantly and a restart resumes from the last known items instead of hammering every source at once. A curator loop scores items through an LLM, pre-writes summaries, and rewrites a plain-text "taste" file that the scoring reads back — so rating things nudges future picks. The same story from several outlets collapses into one card naming the others, and a deduplicated Top 10 is the default view on phones. A morning push sends the day's picks over ntfy.
Where it stands. Runs continuously on the home server behind Cloudflare Access. No database — all state is JSON files next to the app.
A pregnancy and baby tracker for two parents to keep in sync.
The problem. New parents in Singapore keep the same information in three places: a development timeline, a feed and sleep diary, and a pile of half-remembered advice.
What I built. A single-page vanilla-JavaScript app — no framework, no build step — with a development timeline, a feed and sleep diary with pattern analysis, growth charts, a knowledge base and a family tree. Supabase provides Postgres and auth, with two parents syncing under a shared family code. Playwright end-to-end tests run over the real UI, and it deploys automatically on push to Cloudflare Pages.
Where it stands. In daily use by two people. Installable as a PWA.
Everything above that is self-hosted runs on one Ubuntu Server box in my flat (i5-9400, 32 GB, a GTX 1060 for video). It is a small operations problem, and I treat it like one.
Public without opening a port. Public sites arrive through a Cloudflare Tunnel, so the router has no inbound ports open and origin services bind to 127.0.0.1 only. Each hostname is an explicit ingress rule; the catch-all returns 404. Personal apps sit behind Cloudflare Zero Trust Access with a one-person allow-list.
Family apps stay off the internet. The media hub and the family photo library (Immich, with face search on the GPU) are reachable only over Tailscale. Caddy serves them with real HTTPS certificates through a DNS challenge, bound to the tailnet address, and the tailnet policy gives family members those ports and nothing else.
Comes back by itself. Each app is a systemd service that restarts on failure and runs as an unprivileged user, the jhub API with a read-only view of the system; the media stack and Immich are Docker Compose projects. In September 2026 I moved the whole server from Windows to Ubuntu on a new SSD, from a written runbook.
Backed up, and honest about it. Nightly timers archive every app's data, the environment files and the tunnel config, and a second job mirrors the backups and the photo library to a separate disk without ever deleting. Two disks in one box still do not survive a fire or a theft, so an off-site copy is the next job.
Lessons written down. A sandboxed service kept a disk "busy" through its private mount namespace long after everything else let go of it, and a systemd reload quietly cut the media container off the GPU. Both are in the runbook now, and the second one is a health check.

A browser remake of a Game-of-the-Year student title — playable in one click.
First Place — Claude Comair Grand Prize for Game of the Year · DigiPen Game Awards 2019
The problem. Kubrix was built in 2019 by a nine-person DigiPen Singapore team on a custom C++ engine, and took First Place — the Claude Comair Grand Prize for Game of the Year — at the DigiPen Game Awards. I was one of its four programmers. It then became unrunnable: a Windows build, a 27 GB SVN repository and an engine nobody has compiled since.
What I built. I rebuilt the game for the browser in Three.js from the original models, UI art, music and sound: the platforming and build-mode mechanics, a level pipeline that converts the original XML level and gameplay data to JSON, an OBJ/MTL-to-GLB model converter, checkpoints and localStorage saves, touch controls that switch on automatically on phones, and a quality toggle for weak GPUs. A dependency-free Node WebSocket relay lets you pair a phone by QR code and use it as a gamepad; the relay validates and rebuilds every message so a paired phone can only send controller input. A separate tool does an automated playthrough — walking, jumping and spending stars on bridges — to prove all five shards are still reachable after a level change.
Where it stands. Runs as a fully static build with relative asset paths, self-hosted through Cloudflare Tunnel at kubrix.jasmantan.com. Loads on desktop, phone and TV.
An unofficial fan remake by a member of the original team. Not affiliated with or endorsed by DigiPen; all art, audio and design belong to Team Whiteboard & Markers.
Original team: Peng Sng (Producer), Wei Chin (Technical Lead), Yong Lie (Design Lead), Khet Sim (Programmer), Zhanhao Leong (Programmer), Sing Tan (Programmer), Sian Cheong (Programmer), Zheng Ong (Designer), Dun Ong (Designer). Team Whiteboard & Markers · DigiPen Institute of Technology Singapore · GAM 352 · released 4 December 2019. “Sing Tan” is me.
A phone-and-TV social deduction game refereed by an AI host instead of a person.
The problem. Social deduction games like Blood on the Clocktower need one person to run them, and that person never gets to play. I wanted a version where the referee is software.
What I built. A TypeScript monorepo serving three surfaces from one process: a private phone view, a shared TV view, and a PIN-gated host panel. Fastify 5 and Socket.IO for transport, React 19 on the client. The rules engine is a pure, deterministic package: game state is never stored, only an append-only action log plus the seed and settings, so the server replays a game from its log on boot and re-arms the phase timer — a mid-game crash or restart is survivable. Up to six seats can be AI players. Narration goes through an LLM with a 4-second timeout that falls back to offline templates, so the whole game still works with no internet at all.
Where it stands. Live from the home server through Cloudflare Tunnel: phones open /play, the TV opens /screen. One server process hosts one game at a time — it is an evening with friends, not a public multiplayer service.
Most of my recent code is written by AI coding agents. My job is the part that decides whether it is right: the brief, the boundaries, the tests and the merge.
Every job starts as a written brief: the problem, numbered items, what the agent must never touch, how the result will be checked, and reserved IDs for database migrations and decisions so agents working in parallel never collide.
Each agent works in its own git worktree and branch, with its own test ports and a throwaway copy of the app. No agent touches live data, live services or a deploy.
Agent branches meet in an integration branch, and the whole suite runs there: type checks, about 3,700 unit tests and Playwright browser tests at phone and desktop sizes. Master only fast-forwards to a green build, so rolling back is one command.
Architecture decisions go in a log with rollback notes. When an agent's fix is a guess, it goes back with one rule: write a test that fails before the fix. Anything that touches live data or production stays with me.
Dec 2021 – present
ENNOVI (formerly Interplex) · Singapore
Production workflows on a .NET workflow engine and end-to-end delivery across a six-database SQL Server estate serving six plants, plus the SAP ERP ↔ MES integration layer that runs across them.
Apr 2020 – Nov 2021
NCS Pte Ltd · Singapore
Full-stack development, enhancement and maintenance of client applications at a government-linked IT services firm.
Apr 2019 – Dec 2019
Affinixy Pte Ltd · Singapore
Shipped a mobile game to Android and iOS: core systems, AI, NavMesh pathfinding, gameplay and UI/UX direction.