Portfolio

Case studies

What each system had to solve, what I built and the decisions that mattered. Internal systems are described without screenshots, internal addresses or confidential figures.

Download full portfolio (PDF)

Cemindo

Cemindo · 2026Delivered · continuous improvement

MemoFlow

Sole engineer: Go backend and React SSR front end

Company memos written in a rich-text editor, approved along a chain, e-signed through Mekari and published as PDFs on the letterhead.

The problem

A company memo needs a number in the register, a multi-step approval, a legally valid signature and a clear record of who approved which version, and when.

What I built

  • A TipTap editor feeding a Go service that renders HTML to an A4 PDF with headless Chrome (chromedp), stamps a watermark and uploads it to S3-compatible storage.
  • Approval chains with delegation, recall, cancel and expiry, plus registrars who keep the memo register.
  • Mekari e-signature integration with HMAC-signed requests. Signature positions are found automatically by reading marker coordinates from the PDF's text layer.
  • Notifications in the app (SSE), by debounced email and digest, and on Telegram, each with per-user preferences.
  • Publishing and signing run through outboxes with retries, bounce handling and a manual-publish fallback.

Key decisions

  • When CSS backgrounds didn't survive Chrome's PDF output, the watermark moved to a separate stamping step on the finished PDF.
  • Each submit freezes a memo version, so approvers always sign exactly the text they reviewed.
  • Business rules live in the backend, and the front end follows its JSON:API contract instead of copying the rules.
  • 59 feature slices, 80 migrations and 540 test files, with the domain rules written down per topic.
  • Go
  • Gin
  • PostgreSQL
  • chromedp
  • Mekari e-Sign
  • S3 (Wasabi)
  • SSE
  • React SSR
  • TipTap

Cemindo · 2026Delivered · continuous improvement

Company KPI Dashboard

Rewrote the backend and the web app

Clinker and cement production, domestic sales and safety KPIs shown on office wall screens, with a separate confidential view for the board.

The problem

The office screens must never show confidential tonnages. The first version had one API for every screen, synced every source hourly whether or not it had changed, and returned 0 when a value couldn't be computed.

What I built

  • A Go API with two faces: a public, rate-limited API that only ever returns percentages, and a management API (behind an API key) with the actual figures.
  • Scheduled and manual syncs from the ERP, Excel files on an SMB share, and a Google Drive folder of overlay videos, so the dashboard only ever reads its own Postgres.
  • A React SSR wall-screen app with rotating banners, a BMKG weather theme per site, and an auto-reload when a new version is deployed.

Key decisions

  • Confidential values never leave the server for the public screen. The API key is only added by the server-side proxy.
  • Each KPI card is compared with its target to date, prorated per day, and a value that can't be computed is null instead of 0.
  • Go
  • PostgreSQL
  • SMB
  • Google Drive API
  • React SSR
  • Redux
  • Vitest

Cemindo · 2025 – 2026Delivered · continuous improvement

P2H: Heavy-Equipment Inspection

Main developer of the backend and the CMS

Daily pre-use inspections of heavy equipment, follow-up investigations and fitness checklists, across several business units and sites.

The problem

Operators check every unit before a shift. Failed checkpoints have to become investigations, units have to move between sites, and safety teams need reports per site.

What I built

  • The Strapi backend: checkpoints, investigations with attachments, unit availability history and site moves, Bugar Selamat fitness checklists, Excel exports and email to the safety team.
  • A redevelopment to support multiple business units, each with multiple sites.
  • A responsive React CMS for units, groups, sites, departments, users and shifts.

Key decisions

  • Moved the access policies into middleware, and a user's token is revoked when their account changes, so a role change takes effect immediately.
  • Strapi
  • Node.js
  • MySQL
  • Redis
  • React
  • Vite
  • Docker

Cemindo · 2026Delivered · continuous improvement

TPS Online: Customs Integration

Sole engineer

Bonded-warehouse documents (BC 1.1 inbound, SPPB/PIB outbound) drafted, validated and submitted to Indonesian Customs over SOAP/XML.

The problem

Every vessel arrival and cargo movement at the bonded warehouse (TPS) has to be reported to Bea Cukai. The documents depend on each other, and a wrong quantity is a compliance problem.

What I built

  • A Go REST API that follows the domain: header (vessel) → bill of lading → inbound and outbound documents, each drafted and then sent.
  • A SOAP client that builds the XML payload, wraps it in an envelope and reads Bea Cukai's result.
  • Sign-in through the company SSO (JWT verified against JWKS, session allowlist in Redis), plus an activity log of who did what.

Key decisions

  • Sending validates the whole document chain first. Available weight is computed inside a transaction with row locks, so two outbound documents can't take the same cargo.
  • A feature flag turns the live customs call on or off, so the whole flow could be tested without filing anything real.
  • Go
  • Gin
  • PostgreSQL
  • SOAP/XML
  • Redis
  • sqlc
  • testcontainers

Cemindo · 2026In progress

COP: Permit to Work

Sole engineer

Job Safety & Environmental Analysis and Permit to Work for the safety (SHE) team, with registration approved per plant.

The problem

Contractors and staff register at a plant, admins of that plant approve them, and every plant must always keep at least one admin.

What I built

  • The Go backend foundation: JSON:API 1.1 (content negotiation, include, sparse fieldsets, sort, filter), ES256 auth with rotating refresh tokens, and an admin user directory.
  • The domain rules written down before any code: who approves whom, how companies are tied to plants, and the last-admin guard.

Key decisions

  • Presenting a refresh token that was already used revokes the whole login, so a stolen token stops working as soon as both parties use it.
  • The last-admin check locks the plant row first, so two revokes at the same moment can't both pass.
  • Go
  • Gin
  • PostgreSQL
  • sqlc
  • JSON:API
  • testcontainers

Cemindo · 2025 – 2026In progress

Single Sign-On & Identity Provider

Sole engineer: protocol design, Go service, React SSR web

One login for the company's internal apps, with per-app sessions, device limits and a logout that reaches every app at once.

The problem

The company's internal apps needed one account per person, one place for admins to approve who may use which app, and a logout or a disabled account that takes effect in every app immediately.

What I built

  • An authorization-token flow where the client exchanges an opaque, single-use, Redis-backed token for a session-bound access and refresh token pair.
  • Apps verify ES256 JWTs against the SSO's JWKS and keep their own session allowlist in Redis.
  • A global logout over Kafka (SASL): every app drops the user's access tokens right away instead of waiting for them to expire.
  • Email verification through a transactional outbox with exponential backoff, and a user and app-access approval dashboard.

Key decisions

  • Login takes the same time whether or not the account exists, so response timing can't reveal which emails are registered.
  • Passwords are SHA-256 pre-hashed before bcrypt, so long passwords are never silently cut at bcrypt's 72 bytes.
  • Cleanup workers lock rows with SELECT … FOR UPDATE, so two instances can't race on the same session.
  • A rate limiter guards the auth endpoints, and the number of concurrent device sessions per user is capped.
  • Go
  • Gin
  • PostgreSQL
  • Redis
  • Kafka
  • sqlc
  • React SSR
  • testcontainers

Style Theory

Style Theory · Nov 2023 – Jan 2024

Janio Logistics Integration

Full stack, end to end

A new third-party logistics provider for Singapore, from the database to the ops tooling.

The problem

Style Theory needed Janio for Singapore apparel delivery, return and swap orders without disturbing the other providers already running.

What I built

  • Provider rows, timeslots and coverage areas, plus a Janio bridge service for create, cancel, timeslot and webhook calls.
  • A webhook listener that maps Janio statuses to internal states with idempotent transitions.
  • Ops tooling in the internal web app, return-swap label exports, and alerts to Google Chat.

Key decisions

  • Ran a pilot with operations (one delivery, one swap, one return) before going live.
  • Delivered and Cancelled became terminal states, so a late webhook from any provider can't undo a finished shipment.
  • Scala (Lagom)
  • PostgreSQL
  • Kafka
  • GraphQL
  • React

Style Theory · Jun – Aug 2024

Percentage Discounts on Stripe & Xendit

Backend

The voucher system extended from fixed amounts to percentages, across two payment providers.

The problem

Marketing wanted percentage vouchers limited to particular plans, verticals and user statuses, but the system only knew fixed amounts tied to a single plan.

What I built

  • A discount type (absolute or percentage) with validation from 0.01 to 100, plus Xendit-specific rounding.
  • Multi-plan and vertical eligibility, user-status targeting, and no vouchers across regions.
  • The Stripe webhook now applies vouchers per subscription item, and the invoice pipeline handles both discount types.

Key decisions

  • Moved vouchers out of a loose meta field into a proper relationship, so they could be queried and reported on.
  • Scala (Lagom)
  • PostgreSQL
  • Stripe
  • Xendit
  • Kafka

Style Theory · Jan – Mar 2024, Aug 2025

GCP → AWS Migration

DevOps

Backend and frontend services moved from GCP to AWS, and later an EKS upgrade.

The problem

Every service, database and deploy pipeline had to move clouds with minimal downtime.

What I built

  • Replaced the GCP-specific Helm configuration with AWS-native settings, and routed DNS through Route 53.
  • Migrated PostgreSQL to RDS, and moved auto-deploys from Jenkins to CircleCI with artifacts in JFrog.
  • Upgraded EKS from 1.31 to 1.32 in every environment, after removing deprecated APIs.

Key decisions

  • Checked that every pod was ready and every service still worked before switching traffic over.
  • AWS (EKS, RDS, Route 53)
  • Kubernetes
  • Helm
  • Terraform
  • CircleCI

Style Theory · 2024 – 2025

OTP Checkout & Performance

Frontend (React SSR)

Checkout and sign-in rebuilt around OTP verification, then made faster.

The problem

Sign-in, sign-up and checkout were separate flows that behaved differently by region, and the checkout page made duplicate API calls.

What I built

  • One checkout flow with OTP that sends each user to the right next step: new, subscriber, ex-subscriber or window shopper.
  • Vouchers applied automatically from the URL, for guests too, plus saved-card reuse across verticals.
  • Analytics events to Mixpanel, Meta, MoEngage and Braze, including abandoned checkout.

Key decisions

  • The voucher validation API is now called once per page load, and UI render time dropped below 100 ms.
  • React (SSR)
  • GraphQL
  • GTM
  • Mixpanel
  • MoEngage

Style Theory (via Padepokan 79) · 2021 – 2022

Logistics Webhooks out of the Monolith

Backend

Gojek and Versafleet webhooks moved from the monolith into the logistic service, with Kafka events.

The problem

Delivery webhooks were processed inside the monolith, where duplicate and late updates made shipment history messy.

What I built

  • New webhook endpoints with request and response kept identical to the monolith's, checked by side-by-side comparisons.
  • Events published to Kafka and consumed by the shipment service: pickup and delivery dates, auto-unbook, courier details.
  • Duplicate updates are filtered out, and failures are reported to Google Chat.

Key decisions

  • If either leg of a swap shipment fails, the whole swap is cancelled, so no customer is left with half a swap.
  • Scala (Lagom)
  • Kafka
  • PostgreSQL

Freelance

Freelance · Oct 2024 – PresentDelivered · continuous improvement

Dumons Coating Website

Frontend and deployment

The company site of an automotive coating brand, built from a PRD and maintained ever since.

The problem

The brand needed a site that shows who they are and what their products do, and makes it easy for customers to get in touch.

What I built

  • A responsive site covering the vision, mission and brand identity.
  • Product highlights, best-sellers, and detail pages with specs, application steps, ingredients and purpose.
  • Sections for promotions, before-and-after photos and social media content, plus contact by email, WhatsApp and map.
  • Ongoing updates as new products and machines arrive, deployed through Git and Netlify.

Key decisions

  • Started as a Vue.js SPA. In Nov 2026 I ported it 1:1 to React SSR prerendered to static HTML: same URLs, pixel-identical pages, now fast plain HTML with tests.
  • Vue.js
  • React SSR
  • TypeScript
  • Netlify
  • Vitest

Freelance · Sep 2026 – PresentDelivered · continuous improvement

Hörnet Lineup Website

Design, frontend and deployment

The site for Dümons' Hörnet product lineup: an Indonesian landing page and product catalogue built to win customers and partner shops.

The problem

Hörnet launched as its own lineup under Dümons. It needed a site with its own character that presents every product and turns workshops and shops into partners.

What I built

  • A landing page with promo posters, real customer reviews, an FAQ with shop logos, and a partner section ("Bengkel & Toko Mitra") that opens a ready-written WhatsApp message.
  • A product catalogue with a poster-style heading, a die-cut product lineup, product cards and detail pages with a colour switch.
  • A hornet-painter mascot that follows the mouse, and its own brand type and colours.
  • Fast images: an image CDN with srcset, versioned asset URLs cached for a year, and photo and link prefetch.

Key decisions

  • Built on my React SSR boilerplate and prerendered, so visitors get plain HTML and there's no server to pay for.
  • Built AI-assisted with Claude (80 of its 117 commits), with every change reviewed by me and released through development and production branches.
  • React SSR
  • TypeScript
  • Tailwind CSS
  • Webpack
  • Netlify

Personal

Personal · 2026Continuous improvement

Go Vertical-Slice Boilerplate

Author

The foundation I start every Go service from: vertical slices, JSON:API 1.1, ES256 auth and tests against a real Postgres, all ready on day one.

The problem

Every new backend needed the same groundwork. Rebuilding it each time wasted days and let the conventions drift from one service to the next.

What I built

  • Vertical slices: each endpoint is its own package (route → handler → service → store → sqlc queries → tests).
  • Gin and Postgres with sqlc and golang-migrate, plus JSON:API 1.1 (content negotiation, include, sparse fieldsets, sort, pagination, filter).
  • ES256 JWT auth: register, login, refresh-token rotation, logout and /me.
  • gomock unit tests and testcontainers integration tests, structured slog logs with request IDs, and build and deploy scripts with a graceful stop.
  • A working example to copy from: auth, a notes CRUD domain and an admin user directory.

Key decisions

  • Vertical slices instead of layers: each feature lives in one folder, so it can be found, copied and tested on its own.
  • Recurring slice patterns (owned resource, lock-before-write, partial PATCH, list) are written down as rules that teammates and the AI both follow.
  • KPI Dashboard API and COP API both started from it, which is why my services all share one shape.
  • Go
  • Gin
  • PostgreSQL
  • sqlc
  • golang-migrate
  • JSON:API
  • testcontainers
  • gomock

Personal · 2026Continuous improvement

This site (React SSR Boilerplate)

Author

Server-side rendering for React with Express, without a framework, prerendered to static HTML for Netlify, with nine switchable themes.

The problem

I wanted a portfolio that loads instantly as plain HTML and costs nothing to host.

What I built

  • One render function shared by the dev server and the static build, so both produce the same HTML.
  • Behaviour tests for every page, the static build and the theme switcher.

Key decisions

  • A tiny script picks your saved theme before the page paints, so the default theme never flashes first.
  • React
  • Express
  • Webpack
  • TypeScript
  • Tailwind CSS