Alexander Dean
— — —
2026 · clubhive.app · In development

ClubHive

Event booking for community clubs — run clubs, cycling clubs, walking groups. Organisers post events and embed a booking widget on their own website; attendees RSVP with no account needed. The booking is the front door. The community is the value.

Next.js · Neon · Drizzle · Better Auth · Resend

The problem

Small clubs run their events through a patchwork of WhatsApp polls, Google Forms, and Facebook events. Nobody knows how many are coming until they turn up. The tools that do solve booking — Eventbrite and friends — are built for one-off ticketed events: heavy, fee-laden, and they keep the attendee relationship for themselves.

I saw this first-hand building a website for a cycling club in my freelance practice. The site looked the part, but the moment someone wanted to actually join a ride, the experience fell off a cliff into a group chat. The club also had no memory: no record of who came back week after week, or who drifted away.

The idea

ClubHive treats every RSVP as the start of a relationship, not a transaction. Each booking builds the club's own attendee ledger — who's new, who's loyal, who's lapsed — and when the next event goes live, the club can reach all of them directly. Organisers get a lightweight CRM they never had to fill in; attendees get a one-tap RSVP and never see a password field.

My role

Solo build — product design, data model, and implementation. The project is also a deliberate exercise in the modern serverless Postgres stack: Neon's database branching for environments, Drizzle for end-to-end type safety, and Better Auth's organisations plugin doing the multi-tenancy heavy lifting.

— Architecture

Frontend
Framework
Next.js App Router · TypeScript
UI
Tailwind CSS · shadcn/ui — mobile-first
Embed
Stripped-down iframe booking widget
Theming
Widget inherits the club's brand colour
Hosting
Vercel
Data
Database
Neon — serverless Postgres
ORM
Drizzle — type-safe, Neon-native driver
Environments
Neon branching for dev / staging
Model
Clubs → events → bookings → attendees
Attendees
One record per person per club
Platform
Auth
Better Auth — organisers only
Multi-tenancy
Organisations plugin maps to clubs
Email
Resend — confirmations & announcements
Payments
Architected for Stripe — not in MVP
Live spots
Polling now, websockets later

— Key decisions

Attendees never create accounts
Context

The audience is someone deciding on a Tuesday night whether to join Sunday's ride. Any sign-up wall at that moment costs the club the booking — and small clubs can't afford to lose a single one.

Outcome

RSVP is a name, an email, and a tap. Every booking carries a unique token; the confirmation email includes a cancel link keyed to it. Attendees get full control of their booking without ever seeing a password field.

Better Auth organisations instead of hand-rolled multi-tenancy
Context

Clubs are textbook tenants: owned by an organiser, possibly co-administered later, with every event and attendee scoped underneath. Building that scoping by hand is where solo projects leak data.

Outcome

The organisations plugin maps one-to-one onto clubs, so membership, ownership, and future co-admin invites ride on a maintained auth layer. Tenant scoping lives in one place instead of being re-implemented per query.

Attendees are club-scoped records, not global users
Context

A person who rides with two clubs is two different community members. A global attendee profile would leak one club's audience to another and make consent murky — whose mailing list are they on?

Outcome

One attendee record per person per club, accumulated across bookings. Each club owns its list outright: events attended, first seen, last seen, marketing consent. Privacy boundary and CRM in the same design move.

Embed-first distribution
Context

Clubs already have websites they're proud of — often built by a local freelancer. Asking them to send members to a third-party platform breaks their brand and hands their audience to someone else.

Outcome

The booking form ships as a single iframe snippet that inherits the club's brand colour and looks native on the host site. The club's website stays the front door; ClubHive is the machinery behind it.

Neon branching as the staging strategy
Context

Booking data is exactly the kind of state you don't want to test against in production, but maintaining a separate seeded staging database is busywork for a solo builder.

Outcome

Every feature branch gets a database branch — production-shaped data, isolated writes, deleted when merged. Schema migrations via Drizzle run against the branch before they ever touch production.

— Technical depth

The embed widget

The widget is the product's distribution strategy. It's a stripped-down route rendered inside an iframe — no nav, no marketing, just the next events and an RSVP form. The club sets a single brand colour at creation, and the widget derives its entire theme from it, so the embed reads as part of the host site rather than a third-party bolt-on. Live spots remaining update by polling — simple and sufficient at this scale, with a clean upgrade path to websockets.

Club brand colour

One colour, set once when the club is created. The widget derives its whole theme from it — buttons, capacity bar, confirmation state.

Embed snippet
<iframe
  src="clubhive.app/embed/sunday-riders"
  width="100%" height="480"
/>

Paste once — works on any site

SRSunday Riders CC
Sunday Social — 40km
Sun 21 Jun · 09:00 · Village Hall car park
23 of 30 spots taken7 left
Powered by ClubHive

↑ live demo — embedded on the club's own site

The attendee ledger

Every booking is written against a per-club attendee record — one row per person per club, accumulated across events. From that single table the club gets its community picture for free: first-timers, regulars worth flagging (3+ events), and lapsed members who haven't been seen in 60 days. When a new event is published, everyone who gave consent hears about it — the attendee effectively subscribed to the club the moment they first booked.

E1E2E3E4E5E6Flag
A01loyal
A02loyal
A03lapsed
A04loyal
A05lapsed
A06loyal
A07
A08loyal
A09
A10loyal
A11
A12
First booking Came back
31 bookings · 12 people · 6 loyal · 2 lapsed

Capacity and the waitlist

Capacity is enforced in the database, not the UI. A booking insert runs in a transaction that counts confirmed bookings with the event row locked — two people grabbing the last spot can't both succeed; the second lands on the waitlist with its own confirmation email. Cancellations promote the head of the waitlist automatically, and the cancel link in every email means the whole lifecycle works without a login.

— Design goals

— —
accounts needed to RSVP
— —
snippet to embed anywhere
100%
of the audience owned by the club
3+
events attended flags a loyal member

— Where it goes next

The MVP is deliberately narrow: clubs, events, bookings, emails, embed. The growth direction is the community layer — event announcements to past attendees, engagement insights (which events pull the best turnout, who came once and never returned), and recurring events for the weekly ride. Stripe lands when paid events do; the schema is already shaped for it.

Further out: white-label embeds with custom domains — aimed at freelancers and agencies who want to bundle bookings into the club websites they already build. That loop closing is the long game: the product was born inside a freelance club-site build, and it goes back out through them.

← All case studies