{"id":43173420,"url":"https://github.com/ramonclaudio/tanstack-convex-starter","last_synced_at":"2026-02-01T02:37:49.325Z","repository":{"id":335170898,"uuid":"1144614354","full_name":"ramonclaudio/tanstack-convex-starter","owner":"ramonclaudio","description":"Minimal starter. TanStack Start + Convex + Better Auth.","archived":false,"fork":false,"pushed_at":"2026-01-28T21:26:14.000Z","size":98,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-29T12:10:52.269Z","etag":null,"topics":["better-auth","convex","fullstack","react","shadcn-ui","ssr","starter-template","tailwindcss","tanstack-query","tanstack-router","tanstack-start","typescript","vite"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ramonclaudio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-28T21:14:30.000Z","updated_at":"2026-01-28T21:26:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ramonclaudio/tanstack-convex-starter","commit_stats":null,"previous_names":["ramonclaudio/tanstack-convex-starter"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/ramonclaudio/tanstack-convex-starter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonclaudio%2Ftanstack-convex-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonclaudio%2Ftanstack-convex-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonclaudio%2Ftanstack-convex-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonclaudio%2Ftanstack-convex-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ramonclaudio","download_url":"https://codeload.github.com/ramonclaudio/tanstack-convex-starter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ramonclaudio%2Ftanstack-convex-starter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28965430,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T02:14:24.993Z","status":"ssl_error","status_checked_at":"2026-02-01T02:13:55.706Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["better-auth","convex","fullstack","react","shadcn-ui","ssr","starter-template","tailwindcss","tanstack-query","tanstack-router","tanstack-start","typescript","vite"],"created_at":"2026-02-01T02:37:48.914Z","updated_at":"2026-02-01T02:37:49.316Z","avatar_url":"https://github.com/ramonclaudio.png","language":"TypeScript","readme":"# tanstack-convex-starter\n\nFull-stack starter with TanStack Start, Convex, and Better Auth. Includes authentication (email/password + username), user profiles with avatar uploads, row-level security, role-based access control, rate limiting, audit logging, and SSR.\n\n## Prerequisites\n\n- [Bun](https://bun.sh) runtime\n- [Convex](https://convex.dev) account (for cloud backend) or Docker (for local backend)\n\n## Quick Start\n\n```bash\nbun install\nbun run setup        # connects to Convex, generates .env.local, sets secrets\nbun run dev          # starts dev server on http://localhost:3000\n```\n\n\u003e [!TIP]\n\u003e For local Convex backend, run `bun run setup:local` instead, then keep `bunx convex dev --local` running in a separate terminal.\n\nThe setup script handles everything automatically:\n\n1. Runs `convex dev --once` to create/connect the Convex project\n2. Writes `CONVEX_DEPLOYMENT` and `VITE_CONVEX_URL` to `.env.local`\n3. Derives and adds `VITE_CONVEX_SITE_URL` from the deployment name\n4. Generates a `BETTER_AUTH_SECRET` and sets it via `convex env set`\n5. Sets `SITE_URL=http://localhost:3000` via `convex env set`\n\n## Environment Variables\n\n### Local Development (`.env.local` — auto-generated by setup)\n\n| Variable | Source | Example |\n| --- | --- | --- |\n| `CONVEX_DEPLOYMENT` | `convex dev --once` | `dev:your-project-name` |\n| `VITE_CONVEX_URL` | `convex dev --once` | `https://your-project-name.convex.cloud` |\n| `VITE_CONVEX_SITE_URL` | Setup script | `https://your-project-name.convex.site` |\n\n### Convex Dashboard / CLI (server-side secrets)\n\n| Variable | Purpose | Set By |\n| --- | --- | --- |\n| `SITE_URL` | App base URL, used for CORS and auth redirects | Setup script (`http://localhost:3000`) |\n| `BETTER_AUTH_SECRET` | 32-byte base64 secret for signing auth tokens | Setup script (auto-generated) |\n\n\u003e [!NOTE]\n\u003e These server-side secrets are stored in the Convex deployment, not in `.env.local`. View or change them via the [Convex Dashboard](https://dashboard.convex.dev) or `bunx convex env` commands.\n\n### Production\n\n\u003e [!IMPORTANT]\n\u003e You must set environment variables in **both** your hosting provider and the Convex Dashboard for production deployments.\n\nSet in your **hosting provider** (Vercel, Netlify, etc.):\n\n```bash\nCONVEX_DEPLOYMENT=prod:your-project-name\nSITE_URL=https://your-app.vercel.app\n```\n\nSet in **Convex Dashboard** (or via CLI):\n\n```bash\nbunx convex env set SITE_URL https://your-app.vercel.app --prod\nbunx convex env set BETTER_AUTH_SECRET $(openssl rand -base64 32) --prod\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eVite config — how env vars are bridged to client and server\u003c/summary\u003e\n\n`vite.config.ts` makes these available on both client and server via `process.env.*`:\n\n- `VITE_CONVEX_URL` — Convex API endpoint\n- `VITE_CONVEX_SITE_URL` — Convex site URL (auto-derived from `CONVEX_DEPLOYMENT` if not set)\n- `CONVEX_SITE_URL` — alias for server-side access\n- `SITE_URL` — app base URL (defaults to `http://localhost:3000`)\n\n\u003c/details\u003e\n\n## Stack\n\n**Frontend:**\n\n- [TanStack Start](https://tanstack.com/start) — full-stack React framework with SSR\n- [TanStack Router](https://tanstack.com/router) — file-based, type-safe routing\n- [TanStack Query](https://tanstack.com/query) — data fetching, caching, SSR query integration\n- [TanStack Form](https://tanstack.com/form) — form state management with Zod validation\n- [React 19](https://react.dev) — UI library\n- [Tailwind CSS v4](https://tailwindcss.com) — utility-first styling (OKLch color space)\n- [shadcn/ui](https://ui.shadcn.com) — Radix-based component library (new-york style, zinc base)\n- [Lucide React](https://lucide.dev) — icon library\n- [Zod](https://zod.dev) — schema validation (v4)\n\n**Backend:**\n\n- [Convex](https://convex.dev) — real-time backend (database, serverless functions, file storage)\n- [Better Auth](https://better-auth.com) — authentication via `@convex-dev/better-auth`\n- [convex-helpers](https://github.com/get-convex/convex-helpers) — RLS, triggers, migrations, custom functions, relationships\n- [@convex-dev/rate-limiter](https://github.com/get-convex/rate-limiter) — token bucket \u0026 fixed window rate limiting\n\n## Project Structure\n\n\u003cdetails\u003e\n\u003csummary\u003eExpand full project tree\u003c/summary\u003e\n\n```text\n├── src/\n│   ├── components/\n│   │   ├── ui/                    # shadcn/ui primitives (avatar, button, dropdown, sidebar, etc.)\n│   │   ├── app-sidebar.tsx        # Main app sidebar (logo, nav, user menu)\n│   │   ├── mode-toggle.tsx        # Light/dark/system theme switcher\n│   │   ├── nav-main.tsx           # Primary nav items (collapsible)\n│   │   ├── nav-secondary.tsx      # Secondary nav items (flat)\n│   │   ├── nav-user.tsx           # User auth status + sign out\n│   │   ├── site-header.tsx        # Alternative header component\n│   │   └── theme-provider.tsx     # Theme context (localStorage, system detection, flash prevention)\n│   ├── hooks/\n│   │   └── use-mobile.ts          # Mobile breakpoint detection (768px)\n│   ├── lib/\n│   │   ├── auth-client.ts         # Better Auth client (convex + username plugins)\n│   │   ├── auth-server.ts         # Server-side auth (token management, SSR helpers)\n│   │   ├── convex-cache.tsx       # ConvexQueryCacheProvider wrapper (5min expiry, 250 max entries)\n│   │   └── utils.ts               # cn() — clsx + tailwind-merge\n│   ├── routes/\n│   │   ├── __root.tsx             # Root layout (providers, sidebar, SSR auth token)\n│   │   ├── index.tsx              # Home page (personalized greeting)\n│   │   ├── auth.tsx               # Sign in / sign up (email, username, avatar upload)\n│   │   ├── profile.tsx            # User profile (edit name, username, bio, avatar)\n│   │   ├── $.tsx                  # 404 catch-all\n│   │   └── api/auth/$.ts          # Better Auth API handler (GET/POST)\n│   ├── router.tsx                 # TanStack Router config + Convex QueryClient\n│   ├── routeTree.gen.ts           # Auto-generated route tree (do not edit)\n│   └── styles.css                 # Global styles + CSS variables (OKLch light/dark themes)\n├── convex/\n│   ├── _generated/                # Auto-generated types \u0026 API (do not edit)\n│   ├── schema.ts                  # Database schema (users, auditLogs, migrations)\n│   ├── auth.ts                    # Better Auth integration + user helpers\n│   ├── auth.config.ts             # Auth provider configuration\n│   ├── authMutations.ts           # Auth mutations (password, email, sessions, delete account)\n│   ├── convex.config.ts           # App config (betterAuth + rateLimiter components)\n│   ├── errors.ts                  # Structured error codes \u0026 factory functions\n│   ├── functions.ts               # Custom function wrappers (authQuery, authMutation, RLS, RBAC)\n│   ├── helpers.ts                 # Re-exports from convex-helpers (relationships, utilities)\n│   ├── http.ts                    # HTTP endpoints (/api/health, /api/users) with CORS\n│   ├── migrations.ts              # Database migrations (addDefaultRole, backfillTimestamps)\n│   ├── pagination.ts              # Cursor-based pagination helpers\n│   ├── rateLimit.ts               # Rate limiter config (apiRead, apiWrite, userAction, criticalAction)\n│   ├── security.ts                # Row-level security rules + RBAC wrappers\n│   ├── testing.ts                 # Test utilities (mock users, assertions)\n│   ├── triggers.ts                # Database triggers (audit logging on user changes)\n│   ├── users.ts                   # User CRUD (getMe, getUser, updateProfile, avatar management)\n│   ├── validators.ts              # Centralized validators (pagination, profiles)\n│   ├── zodFunctions.ts            # Zod-validated function builders\n│   └── tsconfig.json              # Convex TypeScript config\n├── scripts/\n│   └── setup.ts                   # Project setup automation\n├── public/\n│   └── favicon.ico                # Static favicon\n├── package.json\n├── tsconfig.json                  # Path aliases: @/* → src/*, @convex/* → convex/*\n├── vite.config.ts                 # SSR config, env vars, plugins\n├── eslint.config.js               # @tanstack/eslint-config\n├── prettier.config.js             # No semis, single quotes, trailing commas\n├── components.json                # shadcn/ui config\n└── .cta.json                      # Create TanStack App metadata\n```\n\n\u003c/details\u003e\n\n\u003e [!CAUTION]\n\u003e Do not edit files in `convex/_generated/` or `src/routeTree.gen.ts` — these are auto-generated by Convex and TanStack Router respectively.\n\n## Database Schema\n\n\u003e [!NOTE]\n\u003e Better Auth manages its own tables (user, session, account, verification) separately via the `@convex-dev/better-auth` component. The tables below are app-specific.\n\n### `users`\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `authId` | `string` | Better Auth user ID (indexed) |\n| `email` | `string` | User email (indexed) |\n| `username` | `string?` | Normalized lowercase username (indexed, unique) |\n| `displayUsername` | `string?` | Original casing username |\n| `firstName` | `string?` | Parsed from Better Auth name |\n| `lastName` | `string?` | Parsed from Better Auth name |\n| `avatar` | `StorageId \\| null?` | Uploaded avatar (overrides auth provider image) |\n| `bio` | `string?` | User bio |\n| `role` | `'user' \\| 'admin' \\| 'moderator'?` | Defaults to `'user'` |\n| `createdAt` | `number?` | Creation timestamp |\n| `updatedAt` | `number?` | Last update timestamp |\n\n**Indexes:** `email`, `authId`, `username`, `createdAt`\n\n### `auditLogs`\n\nImmutable log of user and system actions, written by [database triggers](#audit-logging).\n\n| Field | Type | Description |\n| --- | --- | --- |\n| `action` | `AuditAction` | Action type (e.g. `user.created`, `auth.sign_in`) |\n| `userId` | `Id\u003c'users'\u003e?` | App user ID |\n| `authUserId` | `string?` | Better Auth user ID |\n| `targetId` | `string?` | Affected resource ID |\n| `targetType` | `string?` | Affected resource type |\n| `metadata` | `any?` | Additional context |\n| `timestamp` | `number` | Event timestamp |\n\n**Actions:** `user.created`, `user.updated`, `user.deleted`, `user.role_changed`, `auth.sign_in`, `auth.sign_out`, `auth.password_changed`, `auth.email_changed`, `profile.updated`\n\n**Indexes:** `userId`, `action`, `timestamp`, `userId_timestamp`\n\n### `migrations`\n\nManaged by convex-helpers. Tracks executed database migrations.\n\n## Authentication\n\nBuilt on [Better Auth](https://better-auth.com) with the `@convex-dev/better-auth` Convex component.\n\n**Sign-in methods:**\n\n- Email + password\n- Username + password\n\n**Sign-up flow:**\n\n1. Name, email, password (required) + username, avatar (optional)\n2. Username validation: 3–30 chars, alphanumeric + underscore/dot, checked against 18 reserved names, real-time availability check (500ms debounce)\n3. Avatar upload: `image/*` only, max 5MB, uploaded to Convex file storage\n\n**Session config:**\n\n| Setting | Value |\n| --- | ---: |\n| Expiration | 7 days |\n| Refresh | After 1 day |\n| Fresh session | 10 minutes |\n| Cookie cache | 5 minutes (compact strategy) |\n\n\u003cdetails\u003e\n\u003csummary\u003eAuth rate limits (HTTP layer)\u003c/summary\u003e\n\n| Endpoint | Limit |\n| --- | ---: |\n| `/sign-in/*` | 5/min |\n| `/sign-up/*` | 3/min |\n| `/forgot-password` | 3/hour |\n| `/reset-password/*` | 3/min |\n| `/send-verification-email` | 3/min |\n| `/list-sessions` | 30/min |\n| `/get-session` | 60/min |\n\n\u003c/details\u003e\n\n**SSR auth flow:**\n\n1. Root route fetches auth token via `createServerFn`\n2. Token is set on `convexQueryClient` before render\n3. Authenticated queries work during server-side rendering\n4. Client hydrates with the same auth state\n\n**Auth mutations available:** change password, forgot/reset password, update email, resend verification, delete account, list/revoke sessions.\n\n## Security\n\n### Row-Level Security (RLS)\n\nDefined in [`convex/security.ts`](convex/security.ts). Default policy: **deny**.\n\n| Table | Read | Insert | Modify |\n| --- | --- | --- | --- |\n| `users` | Public | Deny (auth triggers only) | Owner only |\n| `auditLogs` | Deny (admin queries bypass) | Allow (system/triggers) | Deny (immutable) |\n\n### Role-Based Access Control (RBAC)\n\n\u003cdetails\u003e\n\u003csummary\u003eCustom function wrappers in \u003ccode\u003econvex/functions.ts\u003c/code\u003e\u003c/summary\u003e\n\n| Wrapper | Auth Required | RLS | Role |\n| --- | :---: | :---: | --- |\n| `authQuery` / `authMutation` | Yes | No | Any |\n| `optionalAuthQuery` / `optionalAuthMutation` | No | No | Any |\n| `queryWithRLS` / `mutationWithRLS` | No | Yes | Any |\n| `authQueryWithRLS` / `authMutationWithRLS` | Yes | Yes | Any |\n| `adminOnlyQuery` / `adminOnlyMutation` | Yes | No | `admin` |\n| `moderatorQuery` / `moderatorMutation` | Yes | No | `admin` or `moderator` |\n\n\u003c/details\u003e\n\n### Rate Limiting\n\nApplication-level rate limits via [`@convex-dev/rate-limiter`](https://github.com/get-convex/rate-limiter) (component-based, manages its own tables):\n\n| Name | Rate | Burst | Use Case |\n| --- | ---: | ---: | --- |\n| `apiRead` | 100/min | 20 | HTTP GET endpoints |\n| `apiWrite` | 30/min | 10 | HTTP POST/PUT endpoints |\n| `userAction` | 60/min | 10 | Authenticated user actions |\n| `criticalAction` | 10/min | 5 | Sensitive operations |\n\n### Audit Logging\n\nDatabase triggers ([`convex/triggers.ts`](convex/triggers.ts)) automatically log:\n\n- **User insert:** `user.created` with email, name\n- **User update:** `user.role_changed` if role changes, otherwise `user.updated`\n- **User delete:** `user.deleted` with email\n\nAdmin-only queries available: `listAuditLogs`, `getAuditLogsForUser`.\n\n## HTTP API\n\nDefined in [`convex/http.ts`](convex/http.ts) with CORS support (origin from `SITE_URL` env var).\n\n| Method | Path | Auth | Rate Limit | Description |\n| --- | --- | :---: | :---: | --- |\n| `GET` | `/api/health` | No | No | Health check (`{ status, timestamp }`) |\n| `GET` | `/api/users?id=\u003cuserId\u003e` | No | `apiRead` | Public user profile |\n| `GET` | `/api/users/list?cursor=...\u0026limit=...` | No | `apiRead` | Paginated user list |\n| `*` | `/api/auth/*` | — | See [auth limits](#authentication) | Better Auth routes (auto-registered) |\n\n## Routes\n\n| Path | Component | Auth | Description |\n| --- | --- | :---: | --- |\n| `/` | `index.tsx` | No | Home page with personalized greeting |\n| `/auth` | `auth.tsx` | No | Sign in / sign up (redirects if already authenticated) |\n| `/profile` | `profile.tsx` | Yes | Profile editor (redirects to `/auth?redirect=/profile` if unauthenticated) |\n| `/api/auth/*` | `api/auth/$.ts` | — | Server-side Better Auth handler |\n| `/*` | `$.tsx` | No | 404 catch-all |\n\n## Available Scripts\n\n| Script | Command | Description |\n| --- | --- | --- |\n| `setup` | `bun run setup` | Connect to Convex cloud, generate `.env.local`, set secrets |\n| `setup:local` | `bun run setup:local` | Same but with local Convex backend |\n| `dev` | `bun run dev` | Start Vite dev server on port 3000 |\n| `build` | `bun run build` | Production build |\n| `serve` | `bun run serve` | Preview production build |\n| `test` | `bun run test` | Run Vitest |\n| `lint` | `bun run lint` | Run ESLint |\n| `format` | `bun run format` | Run Prettier |\n| `check` | `bun run check` | Prettier write + ESLint fix |\n| `clean` | `bun run clean` | Remove `node_modules`, `dist`, `.output`, `bun.lock` and reinstall |\n\n## Deploy\n\n1. **Deploy Convex to production:**\n\n   ```bash\n   bunx convex deploy --cmd \"bun run build\"\n   ```\n\n2. **Set Convex production environment variables:**\n\n   ```bash\n   bunx convex env set SITE_URL https://your-app.vercel.app --prod\n   bunx convex env set BETTER_AUTH_SECRET $(openssl rand -base64 32) --prod\n   ```\n\n3. **Set hosting provider environment variables** (Vercel, Netlify, etc.):\n\n   ```bash\n   CONVEX_DEPLOYMENT=prod:your-project-name\n   SITE_URL=https://your-app.vercel.app\n   ```\n\n## License\n\nMIT\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framonclaudio%2Ftanstack-convex-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Framonclaudio%2Ftanstack-convex-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Framonclaudio%2Ftanstack-convex-starter/lists"}