Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/samgutentag/bcycle-map/llms.txt

Use this file to discover all available pages before exploring further.

BCycle Map is ready to run locally within minutes of cloning. The frontend renders the Santa Barbara basemap from the first npm run dev:web, and once you deploy the three Cloudflare Workers and point VITE_API_BASE at the read-api URL, live station data flows through automatically. This guide walks you from a fresh clone all the way to a fully connected deployment.

Prerequisites

Before you start, make sure you have:
  • Node 20+ and npm (check with node --version)
  • A Cloudflare account — the free tier is sufficient for a single active system
  • A GitHub account — needed for the parquet compaction GitHub Actions workflow (runs every 3 hours)
  • wrangler is installed as a dev dependency; no global install required

Step-by-Step Setup

1
Clone and install
2
git clone https://github.com/samgutentag/bcycle-map.git
cd bcycle-map
npm install
3
This installs all dependencies including wrangler, vitest, vite, and the full frontend dependency tree (MapLibre GL JS, Deck.gl, DuckDB-WASM).
4
Run the test suite and typecheck
5
Verify the repo is healthy before touching any configuration:
6
Tests
npm test
# Runs 51 Vitest tests — normalize(), parquet round-trips, marker styles
Typecheck
npm run typecheck
# tsc --noEmit — confirms no TypeScript errors across workers and web app
7
All 51 tests should pass on a fresh clone. If any fail, check that you are on Node 20+.
8
Start the frontend dev server
9
npm run dev:web
# Vite at http://localhost:5173 (falls back to 5174 if the port is taken)
10
Open http://localhost:5173 in a browser. The Santa Barbara basemap renders immediately.
11
API calls to /api/... return 404 until a Worker is running. The basemap and map chrome render fine — only the station markers and live data require the read-api Worker. Continue to the deploy steps below to wire those up.
12
Create Cloudflare resources (one-time)
13
If this is a fresh Cloudflare account you need to provision the KV namespace and R2 bucket before deploying Workers.
14
Authenticate:
15
npx wrangler login      # browser OAuth flow
npx wrangler whoami     # note your Account ID for GitHub Secrets later
16
Create the KV namespace:
17
npx wrangler kv namespace create GBFS_KV
npx wrangler kv namespace create GBFS_KV --preview
18
Both commands print an id. Open wrangler.toml, wrangler.read-api.toml, and wrangler.smoke.toml and replace every PLACEHOLDER_REPLACE_AT_DEPLOY with the production id and preview_id respectively.
19
Create the R2 bucket:
20
npx wrangler r2 bucket create bcycle-map-archive
21
In the Cloudflare dashboard, go to R2 → bcycle-map-archive → Settings:
22
  • Public Development URL → Allow Access — save the https://pub-<hash>.r2.dev URL; you’ll need it for VITE_R2_PUBLIC_URL.
  • CORS Policy → Add policy — paste the following to allow the frontend to query parquet directly:
  • 23
    [
      {
        "AllowedOrigins": ["*"],
        "AllowedMethods": ["GET"],
        "AllowedHeaders": ["*"]
      }
    ]
    
    24
    The wildcard CORS origin is safe here — all data comes from the public GBFS feed and is already redistributable. Tighten AllowedOrigins to your Pages domain once you have one.
    25
    Deploy the three Workers
    26
    npx wrangler deploy                                  # bcycle-map-poller (cron every 5 min)
    npx wrangler deploy --config wrangler.read-api.toml  # bcycle-map-read-api (HTTP)
    npx wrangler deploy --config wrangler.smoke.toml     # bcycle-map-smoke (daily cron)
    
    27
    Each command prints a *.workers.dev URL. Save the read-api URL — the frontend needs it in the next step.
    28
    Configure the frontend environment
    29
    Copy the example environment file and fill in your deployed URLs:
    30
    cp .env.example .env.local
    
    31
    Edit .env.local with your actual Workers and R2 URLs:
    32
    VITE_API_BASE=https://bcycle-map-read-api.developer-95b.workers.dev
    VITE_R2_PUBLIC_URL=https://pub-83059e704dd64536a5166ab289eb42e5.r2.dev
    
    33
    VITE_API_BASE is the read-api Worker URL from the previous step. VITE_R2_PUBLIC_URL is the public R2 bucket URL you saved when enabling the public development URL.
    34
    Verify the live data connection
    35
    Wait up to 5 minutes for the first poller cron tick to fire, then verify:
    36
    curl https://bcycle-map-read-api.<your-account>.workers.dev/api/systems/bcycle_santabarbara/current
    
    37
    You should receive a JSON blob with system, snapshot_ts, and an array of ~85 stations. If you get not found, the cron hasn’t fired yet — wait a moment and retry.
    38
    Tail Worker logs in real time while you wait:
    39
    npx wrangler tail bcycle-map-poller
    
    40
    Now restart npm run dev:web — station markers appear on the map and refresh automatically.
    41
    Set up GitHub Actions for parquet compaction
    42
    The poller buffers JSON snapshots in KV throughout each hour. A GitHub Actions workflow seals those into R2 parquet files every 3 hours. Add the following secrets under Settings → Secrets and variables → Actions in your fork:
    43
    SecretWhere to find itCF_ACCOUNT_IDnpx wrangler whoamiCF_KV_API_TOKENCloudflare dashboard → My Profile → API Tokens → “Edit Cloudflare Workers KV Storage” templateCF_KV_NAMESPACE_IDThe production KV id from Step 4R2_ACCESS_KEY_IDCloudflare dashboard → R2 → Manage API Tokens → Create Token (Object Read & Write on bcycle-map-archive)R2_SECRET_ACCESS_KEYShown once when the R2 token is created — save it immediatelyR2_BUCKETbcycle-map-archive
    44
    The workflow at .github/workflows/compact.yml runs every 3 hours at 5 past the hour (5 */3 * * *). Because it seals every finished buffer in one pass, a 3-hour cadence still backfills cleanly even if a run is skipped. Trigger it manually from the Actions tab to test compaction before waiting for the next scheduled run.

    Local Three-Terminal Dev (No Deploy Required)

    If you want to iterate on Worker code without deploying, use wrangler dev with a shared persist directory so both Workers see the same local KV state:
    # Terminal 1 — read-api on port 8787 (matches the Vite proxy)
    npx wrangler dev --config wrangler.read-api.toml --persist-to .wrangler-state
    
    # Terminal 2 — poller on port 8788 with --test-scheduled
    npx wrangler dev --port 8788 --persist-to .wrangler-state --test-scheduled
    
    # Terminal 3 — frontend (no VITE_API_BASE needed; Vite proxies /api → localhost:8787)
    npm run dev:web
    
    Seed data by triggering the poller’s scheduled handler manually:
    curl 'http://localhost:8788/__scheduled?cron=*/5+*+*+*+*'
    
    When running against local Workers, do not set VITE_API_BASE in .env.local (or leave it empty). The Vite dev server proxies /api to localhost:8787 automatically. Setting VITE_API_BASE to the deployed URL would bypass the proxy and hit the production Worker instead.

    Available npm Scripts

    ScriptWhat it does
    npm testRun Vitest once (51 tests)
    npm run test:watchRun Vitest in watch mode
    npm run typechecktsc --noEmit across the whole project
    npm run dev:webVite dev server at http://localhost:5173
    npm run dev:workerwrangler dev for the poller (Miniflare)
    npm run build:webProduction build → dist/
    npm run deploy:workerwrangler deploy (poller only)
    npm run compute-routesCompute station-to-station travel-time matrix via Google Maps Directions API
    npm run compute-popularityCompute per-station popularity rankings from R2 parquet archive
    npm run compute-leaderboardsCompute corridor leaderboards from R2 parquet archive
    npm run compute-corridorsCompute corridor data and write gbfs/systems-index.json to R2

    Next Steps

    Architecture

    Understand the three-Worker design, KV key layout, R2 parquet partitioning, and the hot vs. cold data path split.

    Add a System

    Add any BCycle GBFS system to systems.json and redeploy the poller to start collecting data.

    GitHub Actions

    Learn how the every-3-hours compaction workflow seals KV buffers into R2 parquet using parquet-wasm and apache-arrow.

    API Reference

    Explore every endpoint the read-api Worker exposes, from /current to /trips to /snapshots.

    Build docs developers (and LLMs) love