Case study 02 / 06
Mori
A Modular Platform for Creators
A multi-tenant platform where creators sell courses and digital products on their own domain. A creator dashboard, a student space, and a public storefront on one module kernel, with Stripe Connect settling every sale directly into the creator's own account.
- Project
- Mori
- Year
- 2026
- Role
- Design & development
- Live
- usemori.app
- Stack
- Bun, Hono, React 19, PostgreSQL, Stripe Connect, Drizzle ORM

01/Problem
The problem
A creator who builds an audience on a course platform is renting it. The courses live at the platform's address under the platform's branding, the customer list and the payment relationship belong to the platform, and leaving means starting over from nothing.
I also wanted to know what a course platform looks like when it is designed to be extended rather than closed. Most of them ship a fixed feature set with a plugin story bolted on years later, so anything the platform did not anticipate becomes duct tape across three more subscriptions.
A rented storefront: courses live at the platform's address under the platform's branding, so the audience a creator builds never quite belongs to them.
Someone else's customer relationship: the platform holds the payment account, the customer list, and the records, so leaving means starting over from nothing.
Money on someone else's schedule: sales settle into a platform balance behind thresholds and transfer windows instead of reaching the creator's own account when the sale happens.
Closed by design: platforms ship a fixed feature set, so anything they did not anticipate means duct tape across three more subscriptions.

02/Approach
Goals and approach
Mori is three products on one backend. Creators get a dashboard to build courses, upload and process media, price offers and bundles, manage members, and edit their public pages. Students get their own space with a course player, progress tracking, and a library of everything they have bought. Buyers get a storefront on a subdomain or a fully custom domain, resolved per request from the host header.
Checkout runs through Stripe Connect with direct charges, so sales settle into the creator's own Stripe account rather than a platform balance, and disputes and customer records stay with the creator. Buying a product grants an entitlement, and entitlement events drive everything downstream: access unlocks, the welcome email goes out, and Discord roles sync automatically. Creators export their data and delete their account without asking permission.
Build everything as a module on one contract, so first-party features and future third-party plugins are the same kind of thing.
Give creators, students, and buyers each a real product rather than three views of the same admin panel.
Route every payment through Stripe Connect direct charges into the creator's own account, never a platform balance.
Isolate every tenant per request from the hostname, host in the EU, and make GDPR export and deletion product features rather than a support ticket.

03/Architecture
How it is built
Everything is a module. A small kernel provides the module registry, a tenant-aware event bus, capability-based permissions, per-tenant configuration, and a two-layer lifecycle, with dependency resolution by Kahn's algorithm. Twelve first-party modules sit on that contract: payments, courses, media, media processing, entitlements, community, websites, webhooks, API keys, privacy, and platform billing.
No module calls another directly. They communicate through typed events carrying tenant context, and cross-module dependencies are injected at the composition root. Modules with a student-facing surface export two UI entrypoints, one composed by the creator dashboard and one by the student app, so a single module ships its own sidebar items, widgets, and pages on both sides at once. Errors are values, not exceptions: every fallible operation returns a Result type and error variants are discriminated objects checked exhaustively at compile time.
The stack is Bun and Hono on the server, PostgreSQL 17 with Drizzle, and better-auth for identity, with React 19, Vite, and React Router v7 across the three front ends. Media runs through a job-graph pipeline that fans validation out into image resizing, video transcoding, HLS packaging, PDF covers, and audio waveforms. Tests run in three tiers, with real PostgreSQL through Testcontainers for integration and end-to-end.
A kernel with a module registry, a tenant-aware event bus, capability permissions, and a two-layer lifecycle, carrying twelve first-party modules.
Three front ends on one backend: a creator dashboard, a student space with an authenticated HLS course player, and a storefront renderer driven by a block-based page editor.
Stripe Connect checkout that grants entitlements, and entitlement events that unlock access and sync Discord roles without any module calling another.
A per-tenant theme system and full English and French localisation across every surface.

04/Results
What happened
Mori is live and in open beta at usemori.app, in English and French. Creators run their whole business behind one login: build a course, upload and process video, price offers and bundles, publish a storefront on their own domain, and follow members and sales in one place. Students get a real learning space with a course player and progress tracking instead of a folder of files. Every sale settles into the creator's own Stripe account, and a purchase unlocks access and Discord roles within seconds. Adding a capability to the platform means writing a new module.