Commerce & Operations

Glassblowing Studio Digital Platform

React Router 7NestJSMedusa v2DirectusStripePostgreSQLDocker

Unified five services behind one platform two artists can actually operate

The Problem

A Detroit glassblowing studio was preparing to open its doors with no digital presence and no technical staff. The owners are artists — they needed a system that could handle class enrollment, e-commerce, a gallery, event listings, email outreach, and a CRM without requiring them to learn software or manage multiple SaaS accounts.

The project also had to support three distinct business phases: a pre-construction awareness site to build an audience before the studio existed, a soft launch with shop and gallery, and full operations with classes, events, and a student portal.

Glassblowing Studio Digital Platform — screenshot 1Glassblowing Studio Digital Platform — screenshot 2Glassblowing Studio Digital Platform — screenshot 3

My Role

Solo developer, full ownership — from initial discovery conversations with the client through architecture, implementation, deployment, and ongoing operations. I ran the discovery process, chose the stack, designed the data model, built the frontend and backend, set up infrastructure, and manage the deployment pipeline. This is an ongoing engagement.

The studio didn't exist yet — it was under construction. The owners are artists with no technical background and no bandwidth for software management. I had to build around where they actually were: a system that could go live for audience-building before any content or products existed, then progressively activate features as the business reached each phase. Every architectural decision was shaped by the constraint that the people operating this system would never open a terminal.

Eastside Art Glass — service architecture showing React Router frontend, NestJS BFF, and backend services

Approach

I built a monorepo with four workspaces: a React Router 7 frontend with SSR, a NestJS 11 BFF that proxies all backend services, a Medusa v2 commerce engine, and Directus 11 for content management. The frontend never talks to any backend service directly — every request goes through NestJS, which handles auth, validation, credential management, and cross-service orchestration. I chose this BFF pattern because the frontend needed to coordinate five different services (CMS, CRM, payments, commerce, email), and I wanted all credentials and business logic server-side.

The discovery process shaped the architecture significantly. During planning I mapped out the client's actual operations and realized they couldn't manage manual outreach to different audience segments. That led to the channel-based email system — subscribers choose what they care about (classes, gallery, shop, events, articles, or a monthly digest), and content published in Directus automatically triggers emails to the right audience via Resend. The client never thinks about email; they just manage their content.

  • CMS (Directus) — owns all content: classes, events, gallery, pages, site settings. Block-based page builder lets the owner compose pages without code.
  • CRM (Twenty) — owns all customer data: contacts, channel subscriptions, enrollment records, activity logs. No customer data lives in the CMS.
  • Payments (Stripe) — checkout sessions, webhooks, refunds. Business-rule refund logic lives in the API, not in Stripe config.
  • Commerce (Medusa v2) — product catalog, cart, orders. API proxy built, frontend cart pending on client's POS decision.
  • Email (Resend) — transactional emails plus automated channel digests. 8 React Email templates.

Key Decisions

Studio open/closed gating system. A single boolean in Directus controls the entire transactional surface. When the studio is "closed" (pre-launch), content sections still render if CMS data exists, but CTAs say "Get Notified" instead of "Book Now," the shop and portal are hidden, and the hero promotes the founding list. When flipped to "open," everything activates. I built this because the project has three planned phases and the client needed the site live for awareness long before they could take bookings. Rather than maintaining two separate sites, one toggle controls the experience.

Strict data ownership boundaries. Each system owns its domain and nothing else. Directus never stores customer data. Twenty CRM never stores content. Medusa never stores class bookings. The API is the only thing that talks to all of them. I rejected the simpler approach of putting everything in Directus because it would have created a tangled data model that breaks as soon as you need real CRM features or proper e-commerce. Keeping boundaries clean now means I can swap any service without rewriting the others.

Magic-link auth over passwords for the student portal. The target users are people who took a glassblowing class — they're not logging in daily. HMAC-SHA256 signed tokens with single-use nonces, 15-minute expiry, and timing-safe comparison. No password resets, no credential storage. Rate-limited to 3 requests per email per 15 minutes. I considered OAuth but it added complexity the use case didn't need.

Channel-based email segmentation in the API layer, not the email provider. Resend has a single audience. All segmentation logic — who gets what, when, and why — lives in the NestJS cron jobs that query Twenty CRM by channel subscription. I chose this over Resend's native audience segmentation because the client's channels map directly to Directus content types, and I wanted the CRM to be the single source of truth for subscriber preferences. Content creation is the marketing — no campaign builder needed.

What Was Hard

Orchestrating enrollment across four systems. A single class booking touches Directus (decrement spots, create enrollment record), Stripe (checkout session with 30-minute expiry), Twenty CRM (create/update contact, log enrollment with check-in code and payment status), and Resend (confirmation email). If any step fails, the others need to stay consistent. I handle this with optimistic spot decrement — spots are reserved immediately and restored if the Stripe session expires or the student cancels. Webhook confirmation plus server-side polling as a fallback ensures no orphaned reservations.

Building for a client who can't validate until the studio physically exists. The studio is under construction. I can't test with real classes, real students, or real inventory because none of it exists yet. I built a mock data system with five application states (production open/closed, mock with data, mock closed, mock empty) so I could develop and demo every UI state independently. The client reviews the mock-open state; I test edge cases against mock-empty. When real content goes in, the mock layer drops away.

E-commerce blocked on POS integration. The Medusa v2 API proxy is built and tested in the NestJS layer, but the frontend cart and checkout are on hold. The client needs to choose their in-studio POS system first to ensure online and in-person inventory stay in sync. I structured the commerce module to be POS-agnostic — the integration point is well-defined — but it's a reminder that technical readiness doesn't always mean you can ship.

Eastside Art Glass — class detail page with enrollment flow

The Result

The awareness-phase site is live. Class enrollment with Stripe checkout, tiered refund cancellations, and waitlist auto-promotion are production-ready. The student portal (magic-link auth, enrollment management, waitlist tracking) is built. The CRM syncs contacts with channel preferences and logs every interaction. Six email types are wired — enrollment confirmation with check-in codes, cancellation with refund details, magic links, waitlist notifications, admin alerts, and channel digests with unsubscribe management.

The studio owners manage everything through Directus — classes, events, gallery, pages — without touching code. The channel email system means publishing a new class automatically notifies the right subscribers. The platform is self-hosted on Coolify (CI/CD and deployment) with Plausible analytics, GlitchTip error tracking, structured logging, and health endpoints. When the studio opens, a single boolean flip activates all transactional features.

Want to work together?

I'm looking for a full-stack engineering role. Let's talk.

Get in touch