Pebbly is a full-stack movie and TV show discovery app built with TanStack Start, Cloudflare Workers, D1 (SQLite), Drizzle ORM, Valibot, Clerk, Google Gemini, and TMDB metadata. It has media browsing, persistent watchlists, per-episode progress tracking, custom lists, and AI recommendations based on what you watch.
In-depth architecture docs live in the docs/ folder:
| Document | Covers |
|---|---|
| Architecture | Tech stack, layers, request flows, deployment & CI |
| Server Layer | Nitro, server functions, auth, RBAC, AI engine |
| Client Layer | Routing, data fetching, state, repository pattern, optimistic updates |
| Data Model | Every D1 table, index, constraint & migration |
| Architecture Decisions | ADRs, why the code is shaped the way it is |
| File Reference | A per-file map of the entire repository |
| Contributing | How to keep the docs accurate as the code changes |
- Browse trending, popular, top-rated, upcoming, and curated movie/TV collections.
- Cross-media search with genre, media type, and keyword filters.
- Detail pages with cast/crew info, trailers, season/episode browsers, and an embedded video player.
- Watchlist statuses (
watch-later,watching,done,dropped), per-episode progress, and reaction tags (loved,liked,mixed,not-for-me,recommended). - Custom lists with public share pages (
/c/<id>): owners can edit, reorder (ranked lists), and clone; visitors only see public lists. JSON export/import for watchlists, and sync across devices via Cloudflare D1. - Light/dark/system themes resolved before first paint (no flash of the wrong palette).
- Recommendations from Google Gemini models (
gemini-3.1-flash-lite,gemini-2.5-flash) based on your watchlist and interactions. - A "Picks For You" homepage row that refreshes twice daily and excludes titles already on your watchlist.
- Feedback loop: liking a recommendation adds it to your Pebbly Picks list; disliking one excludes it from future runs.
- Filter generation by watchlist, custom list, genre preferences, or era presets (Classics, 80s, 90s, 2000s, 2010s, 2020s).
- AI-suggested titles are verified against the TMDB API before display, and verified matches are cached.
| Layer | Technology |
|---|---|
| Framework | TanStack Start + TanStack Router + React 19 |
| Host & Compute | Cloudflare Workers (Nitro cloudflare_module preset) |
| Database | Cloudflare D1 (SQLite) via Drizzle ORM |
| Validation | Valibot (Lightweight, modular schema validation) |
| Authentication | Clerk (@clerk/react + @clerk/backend JWT verification) |
| AI Engine | Google Gemini REST API (generativelanguage.googleapis.com via fetch, gemini-3.1-flash-lite + fallback chain) |
| Styling | Tailwind CSS 4, coss ui components on Base UI (@base-ui/react), light/dark/system themes |
| Data Fetching | TanStack Query (React Query) |
| State Management | Zustand |
| Tooling | Vite 7, Prettier (formatting) + Biome (linting), TypeScript, Wrangler |
├── drizzle/ # Drizzle generated SQL migrations for Cloudflare D1
├── server/ # Nitro server routes and scheduled tasks
│ ├── routes/api/health.ts # /api/health endpoint (D1 status check)
│ └── tasks/snapshots.ts # Daily watchlist snapshots cron task
├── src/
│ ├── server/ # Co-located backend server functions & database layer
│ │ ├── db/ # D1 database schema & Drizzle client
│ │ ├── helpers/ # Shared DB logic (watch items, episode sync, snapshots)
│ │ ├── fns/ # Type-safe TanStack Start server functions (rpc guards, watchlist, lists, recs, admin)
│ │ ├── schema/ # Valibot schemas & typed API result contracts
│ │ ├── auth.ts # Clerk server-side JWT verification & user resolution
│ │ ├── prompts.ts # Context-aware prompt builders for AI recommendations
│ │ ├── ai.ts # Gemini AI client with model fallback chain
│ │ └── rbac.ts # Role-based access control & feature flags
│ ├── lib/ # Core utilities, query keys, TMDB queries
│ │ ├── data/ # Optimistic journal, op builders, watchlist queries
│ │ ├── repository/ # Remote/local mutation layer (repository pattern)
│ │ └── query/ # TanStack Query client, provider, key factory
│ ├── components/ # UI components (coss ui on Base UI) & domain widgets
│ │ ├── ui/ # Base UI-based primitives + theming
│ │ ├── homepage-recommendations.tsx # Homepage "Picks For You" row with interaction buttons
│ │ ├── video-player-modal.tsx # Fullscreen-capable responsive video player
│ │ └── media-card.tsx # Reusable media grid/carousel card
│ ├── stores/ # Zustand guest/local stores (watchlist, lists, progress, daily pick)
│ ├── hooks/ # Custom hooks (watchlist, watch progress, theme, recommendations, RBAC)
│ ├── routes/ # TanStack file-based routes (incl. public /c/$id list pages)
│ └── types.d.ts # TypeScript declarations & domain types
├── wrangler.toml # Cloudflare Workers & D1 configuration
├── wrangler.preview.toml # Preview Worker config for cf-* branches
├── drizzle.config.ts # Drizzle Kit migration generator config
└── drizzle.studio.config.ts # Drizzle Studio dashboard config
- Node.js 22+
pnpm(v10+)- A TMDB API Read Access Token
- A Clerk Application
- A Google Gemini API Key
- A Cloudflare Account (with D1 database enabled)
-
Clone the repository:
git clone https://github.com/Swastikdan/Pebbly.git cd Pebbly -
Install dependencies:
pnpm install
-
Configure environment variables: Create a
.envor.dev.varsfile in the project root (see.env.example; local dev secrets live in.dev.vars, production secrets usewrangler secret put):# Clerk Auth VITE_CLERK_PUBLISHABLE_KEY=pk_test_YOUR_CLERK_PUBLISHABLE_KEY VITE_CLERK_ISSUER_URL=https://your-app.clerk.accounts.dev # App URLs VITE_PUBLIC_APP_URL=http://localhost:3000 # TMDB API (read-only public API key, safe for client) VITE_PUBLIC_TMDB_ACCESS_TOKEN=your_tmdb_read_access_token VITE_PUBLIC_TMDB_API_URL=https://api.themoviedb.org/3 # AI Providers (server-only) GEMINI_API_KEY=your_gemini_key # Clerk server-side session verification (@clerk/backend) CLERK_SECRET_KEY=sk_test_YOUR_CLERK_SECRET_KEY CLERK_ISSUER_URL=https://your-app.clerk.accounts.dev # Cloudflare (needed for `pnpm db:studio` dashboard & GitHub Actions) CLOUDFLARE_API_TOKEN=your_cloudflare_api_token CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id
-
Initialize the local database:
pnpm db:migrate:local
-
Run frontend dev server (Vite + Node SSR):
pnpm dev
-
Run full Cloudflare Workers + Miniflare D1 emulator locally:
pnpm preview:cf
-
Run TypeScript validation & Biome linting:
pnpm typecheck pnpm lint
-
Generate new SQL migration after editing
src/server/db/schema.ts:pnpm db:generate
-
Apply migrations to local database (
.wrangleremulator):pnpm db:migrate:local
-
Apply migrations to remote Cloudflare D1 database (Production):
pnpm db:migrate:prod
-
Open Drizzle Studio to view/edit database tables in your browser:
pnpm db:studio
Deployment is automated via GitHub Actions:
ci.ymlruns typecheck, lint, and a build on every PR targetingcloudflare.- On pushes to
cloudflare,deploy.ymlapplies D1 migrations and deploys the production Worker. - On pushes to any
cf-*branch,preview.ymldeploys an isolated preview Worker with its ownpebbly-previewD1 database.
Or deploy manually:
# 1. Apply any pending migrations to production D1
pnpm db:migrate:prod
# 2. Build and publish to Cloudflare Workers
pnpm deploy:cf| Script | Description |
|---|---|
pnpm dev |
Run Vite development server |
pnpm preview:cf |
Build and run full app on local Cloudflare Worker & D1 emulator |
pnpm build |
Build production bundle (Nitro Cloudflare preset) |
pnpm db:generate |
Generate SQL migrations from src/server/db/schema.ts |
pnpm db:migrate:local |
Apply migrations to local Miniflare D1 database |
pnpm db:migrate:prod |
Apply migrations to Cloudflare production D1 database |
pnpm db:studio |
Launch Drizzle Studio database explorer |
pnpm deploy:cf |
Build and deploy directly to Cloudflare Workers |
pnpm typecheck |
Run TypeScript compiler type-checking |
pnpm lint |
Run Biome linter |
pnpm format |
Auto-format codebase with Prettier |
pnpm format:check |
Verify formatting without writing (CI-friendly) |
- Released under the MIT License.
- Movie and TV metadata provided by TMDB.
- AI Recommendations powered by Google Gemini.
- Built with TanStack Start, Cloudflare Workers, D1, Drizzle ORM, Valibot, Clerk, and Tailwind CSS.
Contributions are welcome, see CONTRIBUTING.md and the Code of Conduct.