Skip to content
AP.
02Production

nextanative

The ticketing, collectibles and loyalty platform behind an Indonesian idol group: four apps sharing one typed Postgres schema.

Scale / counted from the repository

deployable apps on one shared type package
4
SQL migrations
84
API route modules
56
admin screens
40+
achievement badges across 5 categories
40+
feature flags
10
locales: en, id, ja
3
sweep interval for expired holds
60s
/nextanative
Role
Solo full-stack engineer
Period
Ongoing
Type
Fan platform · Live commerce
Stack
TypeScript, React 19, Next.js 15, Hono, Cloudflare Workers, Supabase Postgres

01

Overview

nextanative is a pop-punk idol group in Jakarta. Their core product is the cheki, a polaroid photo session with one member of the band, and it used to be sold through WhatsApp, Google Forms and spreadsheets. This is the platform that replaced that: a public site, a members app, a staff console and one API, built as a Turborepo where the business rules live in Postgres rather than in the clients. One fan identity carries every order, card, badge, stamp and voucher, which is what makes a loyalty economy possible at all.

One fan identity, carried through every surface

  1. 01Fan identity
  2. 02Order
  3. 03Card claim
  4. 04Badge earned
  5. 05VOLT wallet
  6. 06Marketplace
  7. 07Public profile

02

The challenge

Cheki demand is a queue of people trying to buy the same few slots at once. Two fans claim the last slot in the same second and the quota is sold twice. Someone holds a slot and never pays, so inventory stays frozen while real buyers are turned away. Member presales are a form link that leaks in minutes. The split between idol, management and photographer is reconciled by hand after every event. None of that is a screen problem. The hard part is keeping a slot sold exactly once, an early access window closed to the wrong tier, and a payout reproducible months later.

03

My contribution

  • Designed the schema across 84 migrations: events, quotas, orders, collectible cards, badges, ledgers, vouchers and support tickets.
  • Wrote the API as 56 Hono route modules on Cloudflare Workers, with JWKS token verification and role middleware.
  • Built all three front ends: the Next.js public site, the Vite fan app and the Vite admin console.
  • Wrote the Postgres functions that hold the risky rules: booking expiry, card claims, stamp redemption and badge eligibility.
  • Set up deployment on Workers and Pages with staging and production environments, secrets and scheduled jobs.

04

Product surfaces

  • Public site

    @nexta/public

    Who uses itEveryone, including search engines

    Built withNext.js 15 App Router, server rendered

    Runs onCloudflare Workers via OpenNext

  • O2SN fan app

    @nexta/fan

    Who uses itRegistered fans

    Built withVite, React 19

    Runs onCloudflare Pages

  • Admin console

    @nexta/admin

    Who uses itStaff, management, photographers, door crew

    Built withVite, React 19

    Runs onCloudflare Pages

  • API

    @nexta/api

    Who uses itThe three clients above

    Built withHono 4

    Runs onCloudflare Workers

05

Inside the product

  1. 01

    Cheki ticketing

    cheki_events · cheki_event_members

    Pre-order and door sales are two channels on one event, each with its own quota, bundle price and form. Quotas can be capped per member or pooled, and a form can gain a field without a migration because the answers land in JSONB.

  2. 02

    The fifteen minute hold

    pg_cron, every 60 seconds

    A fan holds a slot while they transfer the money. A database function expires the hold, returns the quota and flips the state in one statement, so a slow transfer cannot lock inventory for the rest of the day.

  3. 03

    Early access enforced in Postgres

    RLS policies on cheki_submissions

    Member presales are timing windows the database evaluates against the fan's membership tier. A leaked link, an edited request body or a hand written curl call meets the same policy, because there is no check in the client to remove.

  4. 04

    Door sales on a tablet

    Cashier · check-in · shift recap

    The on-the-spot flow is built for one hand and a queue of two hundred: carts across several members, bundle pricing applied automatically, a recap at the end of each shift, and a batch code the crew writes on the back of the physical polaroid.

  5. 05

    Collectibles and achievements

    card_claims · achievements.rules_config

    A purchase can claim a collectible card into a digital binder and earn one of 40 badges. Each badge is a row with a rule, a hint, a progress style and two visual states, so a new achievement is an insert rather than a deploy.

  6. 06

    A currency that cannot pay twice

    volt_ledgers · stamp_ledgers

    VOLT and loyalty stamps are append only ledgers with a unique key on user, reference and type. Balance is a sum, a replayed reward becomes a constraint violation, and every grant can be traced back to what caused it.

06

Technical approach

Quota arithmetic in the database

The sold count is a denormalised column on purpose, because counting submissions on every read would put an aggregate in the hottest screen of the app. It increments only inside the booking function, decrements on rejection and on delete, and a repair function can rebuild it, so drift is recoverable rather than fatal.

one statement, four guards

A hold that cannot become a lock

Expiry is not a client timer. A scheduled database job calls one function a minute, updates the expired rows and returns their quantity to the member quota in the same transaction.

release_expired_bookings()

Early access with no client gate

The presale window is a row level policy. The API never receives an early access decision to make, which means no future client can skip it.

row level security

Ledgers instead of balances

Rewards are written as immutable rows and balances are summed from them. A retried request that would pay twice fails on the constraint instead of quietly crediting twice.

UNIQUE (user_id, reference_id, transaction_type)

Achievements stored as data

Forty badges in five categories share one eligibility function that reads a JSONB rule per row. Adding an achievement is an insert, and its progress style (bar, streak, timer, hidden) travels with the row.

check_vault_eligibility()

One typed contract for four runtimes

Order status, badges and events are declared once and imported by every app. A change to a payment state that would break a screen fails the build first.

@nexta/types

Built with

Monorepo
Turborepo 2pnpm workspacesTypeScript 5.6, strict
Front end
React 19Next.js 15Vite 6TailwindRadixTanStack Queryreact-hook-form + Zod
Back end
Hono 4Cloudflare WorkersSupabase Edge Functions
Data
PostgreSQLRow level securitySECURITY DEFINER functionspg_cron
Integrations
ImageKitFirebase Cloud MessagingMonaco editorjspdf
Translations
next-intli18next

07

Screens

Screenshot slot

Fan app, order history

Order states from pending payment to approved, with the invoice the fan can download.

Screenshot slot

Cashier, live session

The on-the-spot cart at the door, with per member quantities and bundle pricing.

Screenshot slot

Revenue analytics

Per member revenue, fill rate and the split across idol, management and photographer.

Screenshot slot

Voltage ID

The public fan profile: equipped badge, oshi and collection highlights.

08

Where it stands

  • 01

    No automated tests yet

    Correctness rests on database constraints, type checks and manual QA at live events. SQL tests for booking expiry, stamp redemption and card claims come first, then tests around bundle pricing and date handling.

  • 02

    Two shared packages are declared, not extracted

    The shared UI still lives twice, once in the fan app and once in the admin app. The package exists and the build already resolves it; the move itself has not happened.

  • 03

    The gacha terminal is built and switched off

    It ships behind a flag that defaults to off. Drop rates and currency sinks need tuning before it can open.

  • 04

    The admin console is Indonesian only

    Operations staff are internal, so localising it would add maintenance without a reader.

  • 05

    These screenshots are schematics

    The repository is private and the artwork belongs to the band. Each frame on this page is a placeholder that can be swapped for a real capture once permission is in writing.

09

Outcome

At a live event the platform covers the whole loop: registration, pre-order, payment approval, door sales, card claims, badges, loyalty vouchers and the payout report. The numbers that would make the strongest case (orders processed, members onboarded, hours of reconciliation removed) belong to the band, so they are not published here. What is public is the shape of the system and the rules it enforces.

Next project

Leads Master →