FORGE Running Labs is a running community founded by Timothy Allen Olson, a two time Western States 100 champion who set a record running the full 2,653 mile Pacific Crest Trail. The site started as a Replit project with a strong editorial front end: five training journeys from 5K to 50K+, a community calendar, the Lab, and Stories. Behind it there was almost nothing. The API had one health route, the database was empty, and the join and member pages were static mockups with hardcoded data.
The goal was a paid membership business. Members pick a journey, pay monthly, and join live sessions with Timothy without ever leaving the site. It also had to keep running on Replit for now without locking the client into Replit later.
What I built
I built the whole backend: accounts, billing, live video, email, and the infrastructure under all of it. It's an Express 5 and TypeScript API in a pnpm monorepo, with a shared Drizzle schema on Neon Postgres and an OpenAPI contract that generates typed React hooks and Zod validation for the front end. I then connected the existing member pages to real data while keeping their original design.
Source control and environments
Took the project from editing only inside Replit to GitHub, with separate feature branches for auth, billing, and Zoom so each piece could be reviewed and shipped on its own
Set up a sync workflow between local development, GitHub, and Replit, so the client's own Replit Agent sessions and my commits don't overwrite each other
Got the workspace building and running locally outside Replit, with one command starting the API and the site together and Stripe webhooks forwarded to the local server
Neon Postgres with separate main and dev branches, so development never touches production data
A post merge hook that installs dependencies and applies schema changes automatically whenever Replit pulls new code
Documented every production secret, the Stripe live mode setup, and the first deploy checks, so launch day is a checklist
Authentication
Better Auth, self hosted in the Express API, with cookie sessions stored in the client's own Postgres instead of a third party service
Sign up with a journey picker, sign in, sign out, and password reset by emailed link
Member profiles with journey, start date, location, bio, and favorite place. Role and journey start date can only be set on the server, so a member can't make themselves an admin from the browser
Route guards in the React app, plus requireAuth, requireMember, and requireAdmin middleware on every protected API route
Admin access granted with a single command line script
Stripe membership billing
A $79 per month subscription through Stripe Checkout, built on Better Auth's Stripe plugin so billing, sessions, and the database share one system
Signature verified webhooks keep subscription status in sync. Forged or unsigned events are rejected
When a member comes back from Checkout, the API confirms the payment with Stripe directly, so access is instant instead of waiting on a webhook
Stripe customer portal for card updates, invoices, and cancelling at the end of the billing period, configured through the API
Card, Link, ACH bank debit, and Cash App Pay at checkout
Access rules built around how billing actually behaves: a grace period with an update card banner while Stripe retries a failed payment, blocked double subscriptions, and returning members choosing to resume their journey at the week they left off or start over at week 1
Live Zoom sessions inside the site
Zoom REST API through a Server to Server OAuth app with a cached token, creating, updating, and cancelling meetings on the client's Zoom account
An admin scheduling page: pick a host, send a session to every member or a single journey, enter times in your own time zone, and start as host in one click
Zoom's full web client embedded in the member dashboard with the Meeting SDK, including gallery view, chat, participants, and a mic and camera check before joining. The SDK requires React 18 and the site runs React 19, so it runs in an isolated iframe with the SDK files served from the site itself
Sessions open 15 minutes before start and stay open 30 minutes after the end, or any time the host is live, so early starts and long sessions just work. Members see a waiting screen that checks every 20 seconds until the host arrives
Meeting IDs, passcodes, and short lived signed join tokens only go to active members inside the join window, so a leaked meeting link can't be used through the site
An Open in Zoom fallback for Safari and mobile browsers
Email
Resend wired in for transactional email from the FORGE domain, starting with password reset
Safe fallbacks: in development emails print to the console, and in production a missing key logs an error instead of failing silently
Built to move
Every infrastructure choice was made so the platform can leave Replit without a rewrite. Auth lives in the client's own database instead of Replit Auth, the database is Neon instead of Replit's built in Postgres, and the Express API can run as a Vercel Function with nothing more than a new entry file. Stripe and Zoom just get a new webhook URL.
Tested end to end
API flows tested with curl and browser flows tested with Playwright
Real Stripe Checkout runs with test cards, plus the full webhook lifecycle through the Stripe API
A real Zoom meeting created on the FORGE account and joined from inside the site
The bundled production API boots with no node_modules installed