Chain Seeker · 2025 · Ongoing
A research platform indexing the long tail of disc golf — courses, discs, pro players, and tournament results — paired with private leagues for the people who actually play. Co-built with Benjamin Hier.
Disc golf has a data problem that the existing apps don't solve. Player count has roughly tripled in the last decade, but most of the digital infrastructure — course directories, disc databases, tournament feeds — sits behind static directories or hand-maintained spreadsheets. If you're a serious player trying to research a course before a road trip, or compare two discs across the manufacturers that don't show up on the big retailers, or follow a pro through their season, you're stitching together six tabs.
There's no single research surface for disc golf. PDGA is the source of truth for tournaments and courses, but its UI predates the modern web. Disc manufacturers publish flight numbers and not much else. Retailer inventories are siloed. Standings, ratings, and rankings live in disconnected systems.
We — meaning Benjamin Hier and I — wanted to fix that. Chain Seeker is the result: a research platform with deep catalogs, plus a private-league layer for the people who actually play.
Three things, integrated:
Indexed from the PDGA Course Directory, enriched with hand-curated descriptions, hole counts, geocoded coordinates, and letter-grade rankings derived from user reviews.
Pulled from the PDGA's official approved-discs CSV, plastic-line variants synced from four retailers (Infinite Discs, OTB, Marshall Street, Amazon affiliate), price + stock tracked on a nightly cadence.
Profiles, season standings, world rankings, event results — synced from PDGA Live + StatMando + DGPT, with our own derived stats on top.
Plus private leagues with events, standings, dues, and a director↔player DM channel that doesn't try to be a social network.
The instinct with this much data is to split — separate services for ingestion, search, leagues, payments. We didn't. Chain Seeker is a single Next.js 16 application talking to a single Neon Postgres instance via Drizzle ORM, with the ingestion jobs running as cron-scheduled scripts on the same deployment.
One database. One application. One deploy. The complexity budget goes to the domain model — 115 tables, real foreign keys, real enums — not to the operational topology. Two builders shipping a research platform shouldn't be paying microservice taxes.
The decision is paying off. New features touch one schema, one auth boundary, one cache layer. When the leagues product launched, the dues column landed in the existing local_leagues table; the Stripe integration reused the customer model we already had.
Both work. We picked Drizzle for two reasons: the SQL is yours when you need it (raw queries are first-class, not an escape hatch), and the schema-as-TypeScript pattern means refactors compile-check across the whole codebase. With 115 tables, that compile-check is the safety net.
export const courses = pgTable("courses", {
id: serial("id").primaryKey(),
slug: varchar("slug", { length: 200 }).notNull().unique(),
name: text("name").notNull(),
city: text("city"),
state: varchar("state", { length: 2 }),
country: varchar("country", { length: 2 }),
holes: integer("holes"),
latitude: doublePrecision("latitude"),
longitude: doublePrecision("longitude"),
letterGrade: varchar("letter_grade", { length: 2 }),
stateRank: integer("state_rank"),
countryRank: integer("country_rank"),
pdgaCourseId: integer("pdga_course_id"),
// ... and 25 more columns
});The data is only useful if it stays fresh. Rather than wire every scraper into the Next.js app directly, we offloaded the ongoing data work to a separate steward — a Python agent (Aileen) that owns nightly ingestion, drift detection, scoring updates, and integrity audits. The agent writes to Postgres; the web app reads from Postgres. The boundary is the schema.
Live in production, paying customers using the leagues product, the research catalogs growing daily. The platform is the proving ground for a pattern I keep coming back to in client work: a tight schema, a single application, a separate agent for the data work, ship.
A research platform that grew from zero to 11,500 courses, 1,164 discs, and 792 pro players in eight months — with the operational surface of a single Vercel project and a single Neon database. The follow-on Aileen agent now runs the entire ingestion pipeline unattended, freeing the build team to ship product instead of fight scrapers.
The next twelve months are about turning research depth into network depth: course favorites and home-course identity, tournament check-ins as a first-class flow, a clean public standings page for league directors. The infrastructure was sized for the next 10x already.