All work

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
Stack
Bun, Hono, React 19, PostgreSQL, Stripe Connect, Drizzle ORM
Mori – A Modular Platform for Creators screenshot
Fig. 01Mori, 2026

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.

Mori: research and exploration
Fig. 02Mori: research and exploration

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.

Mori: the solution in use
Fig. 03Mori: the solution in use

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.

Mori: system architecture
Fig. 04Mori: system architecture

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.

Visit usemori.app

Ninibot

Ask me anything about Nicolas Laforet and his work.