~/projectsDavis Brown

1 project · 3 packages · 2026

Projects

One product, built end to end. Below is the case study: what it is, how it’s built, and the decisions that mattered.

● live · iOS · Android · web

Pull Up

Find live pickup basketball. A map of courts showing who's playing right now. Court locations are crowd-sourced and seeded from OpenStreetMap; live activity comes from geo-verified check-ins and crowd reports.

The pull-up map, showing court pins across Lower Manhattan The pull-up map, showing court pins across Lower Manhattan
A court detail screen: hoops, lights, surface and live headcount A court detail screen: hoops, lights, surface and live headcount
Searching for a court or neighbourhood by name Searching for a court or neighbourhood by name
role
Sole engineer, schema to gesture
server
Go · chi · pgx + sqlc · goose · PostGIS
app
Expo · React Native · MapLibre
data
OpenStreetMap seed · OpenFreeMap tiles · 0 API keys

The problem

Knowing where the courts are is the easy half. OpenStreetMap already knows. The question that decides whether you leave the house is whether anyone is on them right now, and nothing answers it. The alternatives are a group chat that covers one court, or driving over to find out.

That made two things the product had to get right, neither of them about drawing a map. A court map with an empty first screen is worthless, so courts seed from OpenStreetMap per viewport and the map is never blank in a city it has not seen before. And a live headcount is only worth checking if it cannot be casually wrong, so check-ins are geo-verified server-side within 150 m of the court and expire on their own two hours later.

How it’s built

server/ Go

A Go API on chi, typed queries via pgx + sqlc, goose migrations. PostGIS does the heavy lifting: radius search, court dedup on import, and geo-verification of check-ins against court polygons.

app/ TypeScript · Expo

One Expo codebase for iOS, Android and web. MapLibre native on device, react-map-gl/maplibre on web, behind one map component. A Barlow-based design system with light and dark runs across every screen.

data/ OSM

Courts seeded from OpenStreetMap, then corrected by the crowd. Tiles from OpenFreeMap: production maps with no API keys and no per-load billing.

Pull Up architecture An Expo client on iOS, Android and web pulls map tiles straight from OpenFreeMap and calls a Cloudflare Worker. The Worker proxies into a Go API running in a Cloudflare Container, which reads and writes a Neon Postgres database with PostGIS and seeds courts from the OpenStreetMap Overpass API. The Worker also holds the R2 photo bucket, transactional email, and an hourly cron that drains background work. OpenFreeMap tiles OSM Overpass Clients iOS · Android · web Expo · MapLibre pull-up-api Cloudflare Worker edge, no cold DB Go API chi · pgx + sqlc Container Neon Postgres + PostGIS R2 photos · email · hourly cron drain no API key court seed

Decisions that mattered

  1. 01

    sqlc over an ORM. Typed Go from real SQL. The queries stay readable, PostGIS functions are first-class instead of escape hatches, and the compiler catches a schema change before the tests do.

  2. 02

    Geo-verified check-ins. A check-in only counts if the device is inside the court polygon. Trust the phone, not the user. It keeps the live count honest without any moderation queue.

  3. 03

    MapLibre + OpenFreeMap. No map API keys, no per-load billing, no vendor that can reprice the core of the product overnight. Native and web renderers sit behind one component.

  4. 04

    Seed, then crowd-source. Courts are seeded from OpenStreetMap per viewport so the map is never empty in a city it has not seen, then players correct and add. Cold start solved with data that already existed.

Try it