Skip to content
← Back to portfolio

Project case study · Mobile system

wDance

Two Expo Router apps sharing a Cloudflare Worker and tRPC backend for social feeds, location-filtered events, profiles, and course storefronts. The project reached staging but never shipped publicly.

React NativeExpoExpo RouterTypeScriptCloudflare WorkerstRPCDrizzle ORMCloudflare D1Cloudflare ImagesCloudflare Stream
wDance

The challenge

wDance had two product entry points: a social and event app for multiple dancers, and a branded storefront for one artist. I needed both clients to share identity, profiles, content, media, and course data without splitting the domain model.

The solution

wDance is an Expo and React Native platform that reached staging on a shared edge backend. The main Expo Router app and white-label demo are separate Expo apps, but both call the same Cloudflare Worker running Hono and tRPC. Drizzle maps a shared TypeScript schema to D1. Users, profiles, posts, events, courses, course videos, and media objects all remain in one domain model.

The monorepo contains four app workspaces: the main Expo client, the white-label Expo client, the Cloudflare Worker backend, and an experimental Next.js app. It also has a shared package. Both product surfaces use one backend contract and one data model. The Worker has explicit staging and production bindings, although this build never shipped publicly.

What I built

Social feed

The backend exposes profile feeds, post lookup and search, and protected mutations for text, image, video, and audio posts. It also handles comments, likes, hashtags, mentions, and shares. Ownership checks run before create, update, delete, like, and comment operations.

Location-filtered event discovery

Events can be filtered by event type, country, state, and city. New records start as DRAFT. Location admins and staff can approve them as PUBLISHED or mark them CANCELLED, and creators receive email notifications. A moderation state machine controls what appears in discovery.

Artist profiles

Profiles include unique usernames, names, bios, dance styles, avatar and banner media, and social links. The profile API and main app keep an artist’s identity separate from individual event listings.

Course and media workflows

The backend supports course and course-video CRUD, including ordering and Cloudflare Stream UIDs for lessons. The demo has a catalog, course and lesson screens, pricing views, and an artist dashboard for creating and editing course content. Images use direct uploads to Cloudflare Images. Videos use Cloudflare Stream, with a status endpoint that polls readyToStream before playback.

Course checkout is not built. The pricing screen renders payment fields and a button, but the handler still marks Stripe and backend integration as a TODO. It only logs the selected course and level. Lesson completion uses local placeholder state rather than a purchase entitlement.

Architecture

Clients: Expo Router and NativeWind power the main app and white-label storefront. The monorepo also contains a separate Next.js app for experimental tRPC and authentication examples.

Edge API and data: Cloudflare Workers with Hono and tRPC provide the API. Drizzle ORM runs over Cloudflare D1 with separate staging and production database bindings and a shared schema package.

Authentication: Email OTPs live in a dedicated KV namespace for a five-minute verification window. After verification, the Worker issues a 10-minute access JWT and a 365-day refresh JWT. It checks access tokens from the Authorization header. A refresh request validates the refresh JWT, looks up the user, checks the refresh-token blacklist KV namespace, and issues a new access token only if the user is not blacklisted. Used OTPs are deleted, and repeated wrong codes trigger a KV-backed cooldown.

Media: Cloudflare Images handles image uploads and delivery metadata. Cloudflare Stream handles video uploads and readiness checks, while lesson records keep the Stream UID.

Deferred integrations: PostHog and RevenueCat are future work only. The EAS profiles contain placeholder secrets, but the source has no PostHog SDK or capture code, RevenueCat or IAP package, or working purchase flow.

wDance reached staging after roughly two years of work, with both product surfaces and the shared backend in place. It never shipped publicly, so there is no live App Store or Play Store release.

Have a project in mind?

Tell me what you're working on and where you need help.