From 13775ea688ea7095e0c969c796ccd2bff1f66dc6 Mon Sep 17 00:00:00 2001 From: gib Date: Tue, 13 Jan 2026 11:11:22 -0600 Subject: [PATCH] Update README.md --- AGENTS.md | 453 ++++++++++++++++++++++++++++++++++++++++++++++++------ README.md | 429 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 733 insertions(+), 149 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 22a1190..626ee8b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,34 @@ # AGENTS.md - Convex Turbo Monorepo +## For AI Agents Working in This Repo + +**Testing & Quality Checks:** + +- **ALWAYS prefer `bun typecheck`** over `bun build` when checking your work + - Typecheck is faster and sufficient for validation + - Build is for production deployment only and takes much longer + +**Scope Focus:** + +- **Ignore `apps/expo/` directory** unless explicitly asked to work on it + - Focus on Next.js app and backend for now + - Expo setup is incomplete and will be addressed later + +**Backend Package Note:** + +- `@gib/backend` has no root `tsconfig.json` (only `convex/tsconfig.json`) + - This is intentional - follows Convex's recommended structure + - Running `bun typecheck` on backend will show TypeScript help output + - This is expected behavior, not an error + +**Self-Hosted Setup:** + +- This project uses self-hosted Convex (not Convex cloud) +- Database runs on separate server via Docker +- See `docker/` directory for deployment configuration + +--- + ## Quick Reference ### Build/Lint/Test Commands @@ -12,14 +41,14 @@ bun dev:expo # Run Expo + Convex backend only bun dev:backend # Run Convex backend only bun dev:expo:tunnel # Expo with tunnel for physical device -# Quality +# Quality (PREFER typecheck over build for testing) +bun typecheck # TypeScript checking (PREFERRED for validation) bun lint # Lint all packages bun lint:fix # Lint and auto-fix bun format # Check formatting bun format:fix # Fix formatting -bun typecheck # TypeScript type checking -# Build +# Build (production only, slower) bun build # Build all packages # Single Package Commands (use Turborepo filters) @@ -32,16 +61,20 @@ bun clean # Clean all node_modules (git clean) bun clean:ws # Clean workspace caches ``` +--- + ## Project Structure ``` convex-monorepo/ ├── apps/ -│ ├── next/ # Next.js 16 web app (@gib/next) -│ └── expo/ # Expo 54 mobile app (@gib/expo) +│ ├── next/ # Next.js 16.0.0 web app (@gib/next) +│ └── expo/ # Expo 54 mobile app (@gib/expo) [IGNORE FOR NOW] ├── packages/ │ ├── backend/ # Convex backend (@gib/backend) -│ │ └── convex/ # Convex functions, schema, auth +│ │ ├── convex/ # Convex functions, schema, auth (synced to cloud) +│ │ ├── scripts/ # Build utilities (e.g., key generation) +│ │ └── types/ # Shared type definitions │ └── ui/ # Shared shadcn/ui components (@gib/ui) ├── tools/ │ ├── eslint/ # @gib/eslint-config @@ -52,6 +85,10 @@ convex-monorepo/ └── .env # Central environment variables ``` +**Note:** Only `packages/backend/convex/` is synced to Convex cloud. Other directories (`scripts/`, `types/`) are kept separate to avoid sync issues. + +--- + ## Dependency Management ### Catalogs (Single Source of Truth) @@ -62,11 +99,12 @@ All shared dependencies are defined in root `package.json` catalogs: "catalog": { "prettier": "^3.6.2", "typescript": "^5.9.3", - "eslint": "^9.38.0" + "eslint": "^9.38.0", + "zod": "^4.1.12" }, "catalogs": { "convex": { "convex": "^1.28.0", "@convex-dev/auth": "^0.0.81" }, - "react19": { "react": "^19.1.4", "react-dom": "19.1.4" } + "react19": { "react": "19.1.4", "react-dom": "19.1.4" } } ``` @@ -76,7 +114,8 @@ All shared dependencies are defined in root `package.json` catalogs: "dependencies": { "convex": "catalog:convex", "react": "catalog:react19", - "typescript": "catalog:" + "typescript": "catalog:", + "zod": "catalog:" }, "devDependencies": { "@gib/eslint-config": "workspace:*", @@ -86,7 +125,7 @@ All shared dependencies are defined in root `package.json` catalogs: ### Updating Dependencies -**IMPORTANT:** Do NOT use `bun update` directly - it may replace catalog: with versions. +**IMPORTANT:** Do NOT use `bun update` directly - it may replace `catalog:` with hard-coded versions. ```bash # Correct workflow: @@ -95,22 +134,52 @@ All shared dependencies are defined in root `package.json` catalogs: 3. Verify with: bun lint:ws (runs sherif) ``` +--- + ## Code Style Guidelines ### Imports (via @gib/prettier-config) ```typescript // Order: Types → React → Next/Expo → Third-party → @gib/* → Local +import type { Metadata } from 'next'; import { useState } from 'react'; import { useRouter } from 'next/navigation'; -import { api } from '@/convex/_generated/api'; +import { useMutation, useQuery } from 'convex/react'; import { ConvexError } from 'convex/values'; +import type { Id } from '@gib/backend/convex/_generated/dataModel.js'; +import { api } from '@gib/backend/convex/_generated/api.js'; // Note .js! + import { cn } from '@gib/ui'; +import { Button } from '@gib/ui/button'; import type { User } from './types'; +import { helper } from '../utils'; ``` +### Convex Imports (.js Extensions) + +**IMPORTANT:** Convex generated files require `.js` extensions due to ESM compatibility: + +```typescript +// ✅ Correct - with .js extension +import type { Id } from '@gib/backend/convex/_generated/dataModel.js'; +// ❌ Wrong - will fail to import +import { api } from '@gib/backend/convex/_generated/api'; +import { api } from '@gib/backend/convex/_generated/api.js'; +``` + +**Why?** The project uses `"type": "module"` in package.json, requiring explicit file extensions for local module imports per ESM specification. + +**Files using this pattern:** + +- `apps/next/src/app/(auth)/profile/page.tsx:12` +- `apps/next/src/components/layout/auth/profile/avatar-upload.tsx:10-11` +- `apps/next/src/components/layout/auth/profile/header.tsx:6` +- `apps/next/src/components/layout/auth/profile/reset-password.tsx:11` +- `apps/next/src/components/layout/auth/profile/user-info.tsx:11` + ### TypeScript - Strict mode enabled (`noUncheckedIndexedAccess: true`) @@ -130,11 +199,16 @@ import type { User } from './types'; ```typescript // Convex functions - use ConvexError import { ConvexError } from 'convex/values'; + throw new ConvexError('User not found.'); // Client-side - handle gracefully -try { ... } catch (e) { - if (e instanceof ConvexError) { /* handle */ } +try { + await updateUser({ name }); +} catch (e) { + if (e instanceof ConvexError) { + toast.error(e.message); + } } ``` @@ -145,6 +219,8 @@ try { ... } catch (e) { - Tailwind classes sorted via prettier-plugin-tailwindcss - Use `cn()` for conditional classes: `cn('base', condition && 'active')` +--- + ## Environment Variables ### Central .env (root) @@ -161,13 +237,24 @@ All env vars in `/.env`. Apps load via `with-env` script: ### Required Variables ```bash -# Convex +# Convex Backend (Self-Hosted) CONVEX_SELF_HOSTED_URL=https://api.convex.example.com -CONVEX_SELF_HOSTED_ADMIN_KEY= +CONVEX_SELF_HOSTED_ADMIN_KEY= CONVEX_SITE_URL=https://convex.example.com -NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com -# Auth (sync to Convex deployment) +# Next.js Public +NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com +NEXT_PUBLIC_SITE_URL=https://example.com +NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com +NEXT_PUBLIC_SENTRY_DSN= +NEXT_PUBLIC_SENTRY_URL= +NEXT_PUBLIC_SENTRY_ORG= +NEXT_PUBLIC_SENTRY_PROJECT_NAME= + +# Server-side +SENTRY_AUTH_TOKEN= + +# Auth (sync to Convex deployment - see below) AUTH_AUTHENTIK_ID= AUTH_AUTHENTIK_SECRET= AUTH_AUTHENTIK_ISSUER= @@ -176,60 +263,161 @@ USESEND_API_KEY= ### Syncing to Convex Deployment +Environment variables needed by backend functions must be synced to Convex: + ```bash -# Upload env vars to self-hosted Convex -npx convex env set AUTH_AUTHENTIK_ID "value" -npx convex env set AUTH_AUTHENTIK_SECRET "value" -npx convex env set AUTH_AUTHENTIK_ISSUER "value" -npx convex env set USESEND_API_KEY "value" -npx convex env set CONVEX_SITE_URL "https://convex.example.com" +# Via CLI (from packages/backend/) +bun with-env npx convex env set AUTH_AUTHENTIK_ID "value" +bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "value" +bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "value" +bun with-env npx convex env set USESEND_API_KEY "value" +bun with-env npx convex env set CONVEX_SITE_URL "https://convex.example.com" + +# Or via Convex Dashboard at your self-hosted URL ``` +--- + ## Convex Backend Patterns +### Package Structure + +``` +packages/backend/ +├── convex/ # Convex functions (synced to cloud) +│ ├── _generated/ # Auto-generated API/types +│ ├── custom/ # Custom auth providers +│ │ └── auth/ +│ │ └── providers/ +│ │ ├── password.ts +│ │ └── usesend.ts +│ ├── auth.config.ts # Auth CORS configuration +│ ├── auth.ts # Auth setup + user queries/mutations +│ ├── crons.ts # Scheduled jobs +│ ├── files.ts # File upload/storage utilities +│ ├── http.ts # HTTP routes for auth +│ ├── schema.ts # Database schema +│ ├── utils.ts # Helper functions +│ └── tsconfig.json # Convex-specific TypeScript config +├── scripts/ # Build scripts (outside convex/) +│ └── generateKeys.mjs # JWT key generation for auth +└── types/ # Shared type exports (outside convex/) + ├── auth.ts # Password validation constants + └── index.ts +``` + +**Note:** Only `convex/` directory is synced to Convex cloud. Other directories (`scripts/`, `types/`) are kept separate to avoid sync issues with Convex deployment. + ### Schema (`packages/backend/convex/schema.ts`) ```typescript -import { defineSchema, defineTable } from 'convex/server'; import { authTables } from '@convex-dev/auth/server'; +import { defineSchema, defineTable } from 'convex/server'; +import { v } from 'convex/values'; export default defineSchema({ ...authTables, - users: defineTable({ ... }).index('email', ['email']), + users: defineTable({ + name: v.optional(v.string()), + image: v.optional(v.id('_storage')), + email: v.optional(v.string()), + emailVerificationTime: v.optional(v.number()), + phone: v.optional(v.string()), + phoneVerificationTime: v.optional(v.number()), + isAnonymous: v.optional(v.boolean()), + }) + .index('email', ['email']) + .index('name', ['name']) + .index('phone', ['phone']), + + profiles: defineTable({ + userId: v.id('users'), + theme_preference: v.optional(v.string()), + }).index('userId', ['userId']), }); ``` ### Queries & Mutations ```typescript -import { v } from 'convex/values'; +import { getAuthUserId } from '@convex-dev/auth/server'; +import { ConvexError, v } from 'convex/values'; import { mutation, query } from './_generated/server'; export const getUser = query({ - args: { userId: v.id('users') }, + args: { userId: v.optional(v.id('users')) }, handler: async (ctx, args) => { - return await ctx.db.get(args.userId); + const userId = args.userId ?? (await getAuthUserId(ctx)); + if (!userId) throw new ConvexError('Not authenticated.'); + + const user = await ctx.db.get(userId); + if (!user) throw new ConvexError('User not found.'); + + return user; + }, +}); + +export const updateUser = mutation({ + args: { + name: v.optional(v.string()), + email: v.optional(v.string()), + image: v.optional(v.id('_storage')), + }, + handler: async (ctx, args) => { + const userId = await getAuthUserId(ctx); + if (!userId) throw new ConvexError('Not authenticated.'); + + await ctx.db.patch(userId, args); }, }); ``` -### Auth +### Auth Setup ```typescript -import { getAuthUserId } from '@convex-dev/auth/server'; +import { Authentik } from '@convex-dev/auth/providers/authentik'; +import { convexAuth } from '@convex-dev/auth/server'; -// In any query/mutation: -const userId = await getAuthUserId(ctx); -if (!userId) throw new ConvexError('Not authenticated.'); +import { Password } from './custom/auth/providers/password'; + +export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({ + providers: [Authentik({ allowDangerousEmailAccountLinking: true }), Password], +}); ``` +**Providers:** + +1. **Authentik** - OAuth SSO provider +2. **Password** - Custom password auth with email OTP verification via UseSend + +--- + ## Next.js Patterns -### Convex Provider Setup +### Convex Provider Setup (Dual-Layer) + +**Root Layout** (`app/layout.tsx`): + +```tsx +import { ConvexAuthNextjsServerProvider } from '@convex-dev/auth/nextjs/server'; + +export default function RootLayout({ children }) { + return ( + + + + {children} + + + + ); +} +``` + +**Client Provider** (`components/providers/ConvexClientProvider.tsx`): ```tsx -// src/components/providers/ConvexClientProvider.tsx 'use client'; import { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs'; @@ -237,11 +425,54 @@ import { ConvexReactClient } from 'convex/react'; const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); -export const ConvexClientProvider = ({ children }) => ( - - {children} - -); +export function ConvexClientProvider({ children }) { + return ( + + {children} + + ); +} +``` + +### Server Component Data Preloading + +```tsx +'use server'; + +import { ProfileComponent } from '@/components/profile'; +import { preloadQuery } from 'convex/nextjs'; + +import { api } from '@gib/backend/convex/_generated/api.js'; + +const ProfilePage = async () => { + const preloadedUser = await preloadQuery(api.auth.getUser); + + return ; +}; + +export default ProfilePage; +``` + +### Client Component Hydration + +```tsx +'use client'; + +import type { Preloaded } from 'convex/react'; +import { useMutation, usePreloadedQuery } from 'convex/react'; + +import type { api } from '@gib/backend/convex/_generated/api.js'; + +interface Props { + preloadedUser: Preloaded; +} + +export function ProfileComponent({ preloadedUser }: Props) { + const user = usePreloadedQuery(preloadedUser); + const updateUser = useMutation(api.auth.updateUser); + + // ... interactive logic +} ``` ### Path Aliases @@ -249,7 +480,30 @@ export const ConvexClientProvider = ({ children }) => ( - `@/*` → `./src/*` (Next.js) - `~/*` → `./src/*` (Expo) -## Expo Patterns +### Middleware (Auth Protection) + +```typescript +import { + convexAuthNextjsMiddleware, + isAuthenticatedNextjs, +} from '@convex-dev/auth/nextjs/server'; + +export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => { + // Redirect authenticated users away from sign-in + if (isSignInPage(request) && (await convexAuth.isAuthenticated())) { + return nextjsMiddlewareRedirect(request, '/'); + } + + // Protect routes + if (isProtectedRoute(request) && !(await convexAuth.isAuthenticated())) { + return nextjsMiddlewareRedirect(request, '/sign-in'); + } +}); +``` + +--- + +## Expo Patterns [IGNORE FOR NOW] ### NativeWind (Tailwind for RN) @@ -267,15 +521,124 @@ import * as SecureStore from 'expo-secure-store'; // Tokens stored via SecureStore, not AsyncStorage ``` +--- + +## Self-Hosted Deployment + +This repo includes Docker configuration for self-hosted Convex deployment in `docker/`. + +**Services:** + +- `convex-backend` - Self-hosted Convex instance (port 3210) +- `convex-dashboard` - Admin dashboard (port 6791) +- `next-app` - Next.js application (standalone build) + +**Quick Start:** + +```bash +cd docker/ +./generate_convex_admin_key # Generate CONVEX_SELF_HOSTED_ADMIN_KEY +# Edit .env with your values +docker compose up -d +``` + +**Network:** Uses external `nginx-bridge` network by default. Change in `compose.yml` if needed. + +See `docker/compose.yml` and `docker/.env.example` for full configuration details. + +--- + ## Known Issues / Cleanup Needed -1. **Apps reference @acme/_ instead of @gib/_** - Legacy T3 template imports -2. **TRPC imports exist but no TRPC** - Replace with Convex client -3. **Expo uses better-auth** - Should use @convex-dev/auth -4. **@gib/ui uses pnpm dlx** - Should use bunx for shadcn +### Next.js App (apps/next/) + +1. ✅ **No @acme references** - Successfully migrated to @gib namespace + +2. ⚠️ **TRPC in middleware matcher** (`src/middleware.ts:32`) + ```typescript + // Can safely remove 'trpc' from this line (unused pattern): + matcher: ['/(api|trpc)(.*)']; + ``` + +### Expo App (apps/expo/) - IGNORE FOR NOW + +3. ⚠️ **@acme TRPC references** (`src/utils/api.tsx:1`, line 49) + - Imports `AppRouter` from `@acme/api` + - Exports `RouterInputs`, `RouterOutputs` from `@acme/api` + +4. ⚠️ **@acme tailwind config** (`postcss.config.js:1`) + - Requires `@acme/tailwind-config/postcss-config` + - Should be `@gib/tailwind-config/postcss-config` + +5. ⚠️ **Uses better-auth** - Should migrate to `@convex-dev/auth` (future work) + +### Backend Package (packages/backend/) + +6. ℹ️ **No root tsconfig.json** - This is intentional, not a bug + - Only `convex/tsconfig.json` exists (Convex requirement) + - Running `bun typecheck` shows TypeScript help message (expected) + - `scripts/` and `types/` folders outside `convex/` to avoid sync issues with Convex cloud + +7. ⚠️ **Hardcoded branding** (`convex/custom/auth/providers/usesend.ts:26`) + - Email templates reference "TechTracker" and "Study Buddy" + - Should be parameterized or use generic branding + +8. ⚠️ **Password validation mismatch** (`convex/custom/auth/providers/password.ts`) + - `types/auth.ts` `PASSWORD_REGEX` requires special characters + - `validatePassword()` function doesn't enforce special characters + - Either update regex or update validation function + +9. ⚠️ **getAllUsers lacks auth** (`convex/auth.ts`) + - Public query accessible to anyone + - Should require authentication or be internal-only + +### UI Package (packages/ui/) + +10. ⚠️ **Uses pnpm dlx** (`package.json:40`) + ```json + "ui-add": "pnpm dlx shadcn@latest add && prettier src --write --list-different" + ``` + + - Should use `bunx shadcn@latest add` instead + - Keep prettier command as-is + +--- ## Adding UI Components ```bash bun ui-add # Interactive shadcn/ui component addition ``` + +**Note:** Currently uses `pnpm dlx` internally (see Known Issues #10). Will be updated to use `bunx`. + +--- + +## Troubleshooting + +### Backend typecheck shows help message + +**Issue:** Running `bun typecheck` from root shows TypeScript help for `@gib/backend`. + +**Explanation:** This is expected. The backend package has no root `tsconfig.json` (only `convex/tsconfig.json`) following Convex's recommended structure. + +**Solution:** No action needed. This is intentional behavior. + +### Imports from Convex fail without .js extension + +**Issue:** TypeScript can't resolve `@gib/backend/convex/_generated/api`. + +**Solution:** Add `.js` extension: `@gib/backend/convex/_generated/api.js` + +**Why:** Project uses `"type": "module"` requiring explicit extensions for ESM. + +### Catalog updates break workspace + +**Issue:** After updating dependencies, packages show version mismatches. + +**Solution:** + +1. Never use `bun update` directly +2. Edit version in root `package.json` catalog +3. Run `bun install` +4. Verify with `bun lint:ws` diff --git a/README.md b/README.md index ecf3de2..fd56d5d 100644 --- a/README.md +++ b/README.md @@ -1,138 +1,359 @@ -# Turborepo starter +# Convex Turbo Monorepo -This Turborepo starter is maintained by Gib. +A production-ready Turborepo starter with Next.js, Expo, and self-hosted Convex backend. Built with TypeScript, Tailwind CSS, and modern tooling. -## Using this example +--- -Run the following command: +## What's Inside? -```sh -npx create-turbo@latest -e https://git.gbrown.org/gib/convex-monorepo.git +### Apps & Packages + +- **`apps/next`** - Next.js 16 web application with App Router +- **`apps/expo`** - Expo 54 mobile application _(in progress)_ +- **`@gib/backend`** - Self-hosted Convex backend with authentication +- **`@gib/ui`** - Shared shadcn/ui component library +- **`@gib/eslint-config`** - ESLint configuration +- **`@gib/prettier-config`** - Prettier configuration with import sorting +- **`@gib/tailwind-config`** - Tailwind CSS v4 configuration +- **`@gib/tsconfig`** - Shared TypeScript configurations + +### Tech Stack + +- **Framework:** Next.js 16 (App Router) + Expo 54 +- **Backend:** Convex (self-hosted) +- **Auth:** @convex-dev/auth with Authentik OAuth & Password providers +- **Styling:** Tailwind CSS v4 + shadcn/ui +- **Language:** TypeScript (strict mode) +- **Package Manager:** Bun +- **Monorepo:** Turborepo +- **Deployment:** Docker (standalone) + +--- + +## Getting Started + +### Prerequisites + +- [Bun](https://bun.sh) (v1.2.19+) +- [Docker](https://www.docker.com/) & Docker Compose (for self-hosted Convex) +- Node.js 22.20.0+ (for compatibility) + +### Development Setup + +#### 1. Clone & Install + +```bash +git clone +cd convex-monorepo +bun install ``` -## What's inside? +#### 2. Configure Environment Variables -This Turborepo includes the following packages/apps: +Create a `.env` file in the project root with the following variables: -### Apps and Packages +```bash +# Convex Backend (Self-Hosted) +CONVEX_SELF_HOSTED_URL=https://api.convex.example.com +CONVEX_SELF_HOSTED_ADMIN_KEY= +CONVEX_SITE_URL=https://convex.example.com -- `next`: a [Next.js](https://nextjs.org/) app -- `expo`: another [Next.js](https://nextjs.org/) app -- `@gib/backend`: a [Convex](https://convex.dev) Backend -- `@gib/ui`: a Shadcn React component library primarily for Next.js (In case we want to add another web application). -- `@gib/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`) -- `@gib/prettier-config`: Prettier configurations -- `@gib/tailwind-config`: Tailwind configurations -- `@gib/typescript-config`: `tsconfig.json`s used throughout the monorepo +# Next.js Public +NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com +NEXT_PUBLIC_SITE_URL=https://example.com +NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com +NEXT_PUBLIC_SENTRY_DSN= +NEXT_PUBLIC_SENTRY_URL= +NEXT_PUBLIC_SENTRY_ORG= +NEXT_PUBLIC_SENTRY_PROJECT_NAME= -Each package/app is 100% [TypeScript](https://www.typescriptlang.org/). +# Server-side +SENTRY_AUTH_TOKEN= -### Utilities - -This Turborepo has some additional tools already setup for you: - -- [TypeScript](https://www.typescriptlang.org/) for static type checking -- [ESLint](https://eslint.org/) for code linting -- [Prettier](https://prettier.io) for code formatting - -### Build - -To build all apps and packages, run the following command: - -``` -cd my-turborepo - -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo build - -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo build -yarn dlx turbo build -pnpm exec turbo build +# Auth (will be synced to Convex) +AUTH_AUTHENTIK_ID= +AUTH_AUTHENTIK_SECRET= +AUTH_AUTHENTIK_ISSUER= +USESEND_API_KEY= ``` -You can build a specific package by using a [filter](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters): +**For local development:** Use `http://localhost:3210` for Convex URLs. -``` -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo build --filter=docs +#### 3. Configure Docker Environment -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo build --filter=docs -yarn exec turbo build --filter=docs -pnpm exec turbo build --filter=docs +Check and update environment variables in `docker/.env` for your deployment: + +```bash +cd docker/ +cp .env.example .env +# Edit .env with your configuration ``` -### Develop +#### 4. Start Self-Hosted Convex -To develop all apps and packages, run the following command: +Spin up the Convex backend and dashboard: -``` -cd my-turborepo - -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo dev - -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo dev -yarn exec turbo dev -pnpm exec turbo dev +```bash +cd docker/ +docker compose up -d convex-backend convex-dashboard ``` -You can develop a specific package by using a [filter](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters): +**Services:** -``` -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo dev --filter=web +- **Backend:** http://localhost:3210 +- **Dashboard:** http://localhost:6791 -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo dev --filter=web -yarn exec turbo dev --filter=web -pnpm exec turbo dev --filter=web +#### 5. Generate Auth Keys & Sync Environment Variables + +Generate JWT keys for Convex Auth: + +```bash +cd packages/backend +bun run scripts/generateKeys.mjs ``` -### Remote Caching +Sync environment variables to Convex deployment (via CLI or Dashboard): -> [!TIP] -> Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache). - -Turborepo can use a technique known as [Remote Caching](https://turborepo.com/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines. - -By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands: - -``` -cd my-turborepo - -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo login - -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo login -yarn exec turbo login -pnpm exec turbo login +```bash +cd packages/backend +bun with-env npx convex env set AUTH_AUTHENTIK_ID "your-value" +bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "your-value" +bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "your-issuer-url" +bun with-env npx convex env set USESEND_API_KEY "your-api-key" +bun with-env npx convex env set CONVEX_SITE_URL "http://localhost:3000" ``` -This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview). +**Important:** For local development, set `CONVEX_SITE_URL` to `http://localhost:3000`. -Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo: +#### 6. Start Development Server -``` -# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended) -turbo link - -# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager -npx turbo link -yarn exec turbo link -pnpm exec turbo link +```bash +# From project root +bun dev:next # Next.js app + Convex backend +# or +bun dev # All apps (Next.js + Expo + Backend) ``` -## Useful Links +**App URLs:** -Learn more about the power of Turborepo: +- **Next.js:** http://localhost:3000 +- **Convex Dashboard:** http://localhost:6791 -- [Tasks](https://turborepo.com/docs/crafting-your-repository/running-tasks) -- [Caching](https://turborepo.com/docs/crafting-your-repository/caching) -- [Remote Caching](https://turborepo.com/docs/core-concepts/remote-caching) -- [Filtering](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters) -- [Configuration Options](https://turborepo.com/docs/reference/configuration) -- [CLI Usage](https://turborepo.com/docs/reference/command-line-reference) +--- + +## Development Commands + +```bash +# Development +bun dev # Run all apps +bun dev:next # Next.js + Convex backend +bun dev:expo # Expo + Convex backend +bun dev:backend # Convex backend only + +# Quality Checks +bun typecheck # Type checking (recommended for testing) +bun lint # Lint all packages +bun lint:fix # Lint and auto-fix +bun format # Check formatting +bun format:fix # Fix formatting + +# Build +bun build # Build all packages (production) + +# Utilities +bun ui-add # Add shadcn/ui components +bun clean # Clean node_modules +bun clean:ws # Clean workspace caches +``` + +### Single Package Commands + +Use Turborepo filters to target specific packages: + +```bash +bun turbo run dev -F @gib/next # Run Next.js only +bun turbo run typecheck -F @gib/backend # Typecheck backend +bun turbo run lint -F @gib/ui # Lint UI package +``` + +--- + +## Project Structure + +``` +convex-monorepo/ +├── apps/ +│ ├── next/ # Next.js web app +│ │ ├── src/ +│ │ │ ├── app/ # App Router pages +│ │ │ ├── components/ # React components +│ │ │ └── lib/ # Utilities +│ │ └── package.json +│ └── expo/ # Expo mobile app (WIP) +│ +├── packages/ +│ ├── backend/ # Convex backend +│ │ ├── convex/ # Convex functions (synced to cloud) +│ │ ├── scripts/ # Utilities (generateKeys.mjs) +│ │ └── types/ # Shared types +│ └── ui/ # shadcn/ui components +│ └── src/ # Component source files +│ +├── tools/ # Shared configurations +│ ├── eslint/ +│ ├── prettier/ +│ ├── tailwind/ +│ └── typescript/ +│ +├── docker/ # Self-hosted deployment +│ ├── compose.yml +│ ├── Dockerfile +│ └── .env.example +│ +├── turbo.json # Turborepo configuration +└── package.json # Root workspace & catalogs +``` + +--- + +## Features + +### Authentication + +- **OAuth:** Authentik SSO integration +- **Password:** Custom password auth with email verification +- **OTP:** Email verification via self-hosted UseSend +- **Session Management:** Secure cookie-based sessions + +### Next.js App + +- **App Router:** Next.js 16 with React Server Components +- **Data Preloading:** Server-side data fetching with Convex +- **Middleware:** Route protection & authentication +- **Styling:** Tailwind CSS v4 with dark mode +- **Analytics:** Plausible (privacy-focused) +- **Monitoring:** Sentry error tracking + +### Backend + +- **Real-time:** Convex reactive queries +- **File Storage:** Built-in file upload/storage +- **Auth:** Multi-provider authentication +- **Type-safe:** Full TypeScript with generated types +- **Self-hosted:** Complete control over your data + +### Developer Experience + +- **Monorepo:** Turborepo for efficient builds +- **Type Safety:** Strict TypeScript throughout +- **Code Quality:** ESLint + Prettier with auto-fix +- **Hot Reload:** Fast refresh for all packages +- **Catalog Deps:** Centralized version management + +--- + +## Deployment + +### Docker (Recommended) + +Build and deploy with Docker Compose: + +```bash +cd docker/ + +# Start all services +docker compose up -d + +# View logs +docker compose logs -f + +# Stop services +docker compose down +``` + +**Services:** + +- `next-app` - Next.js standalone build +- `convex-backend` - Convex backend (port 3210) +- `convex-dashboard` - Admin dashboard (port 6791) + +**Network:** Uses `nginx-bridge` network (configurable in `compose.yml`). + +### Production Checklist + +- [ ] Update environment variables in `docker/.env` +- [ ] Generate `CONVEX_SELF_HOSTED_ADMIN_KEY` +- [ ] Configure reverse proxy (Nginx/Traefik) +- [ ] Set up SSL certificates +- [ ] Sync auth environment variables to Convex +- [ ] Configure backup strategy for `docker/data/` +- [ ] Test authentication flow +- [ ] Enable Sentry error tracking + +--- + +## Documentation + +- **[AGENTS.md](./AGENTS.md)** - Comprehensive guide for AI agents & developers +- **[Convex Docs](https://docs.convex.dev)** - Official Convex documentation +- **[Turborepo Docs](https://turbo.build/repo/docs)** - Turborepo documentation +- **[Next.js Docs](https://nextjs.org/docs)** - Next.js documentation + +--- + +## Troubleshooting + +### Backend typecheck shows help message + +This is expected behavior. The backend package follows Convex's structure with only `convex/tsconfig.json` (no root tsconfig). See [AGENTS.md](./AGENTS.md) for details. + +### Imports from Convex require .js extension + +The project uses ESM (`"type": "module"`), requiring explicit file extensions: + +```typescript +// ✅ Correct + +// ❌ Wrong +import { api } from '@gib/backend/convex/_generated/api'; +import { api } from '@gib/backend/convex/_generated/api.js'; +``` + +### Docker containers won't start + +1. Check Docker logs: `docker compose logs` +2. Verify environment variables in `docker/.env` +3. Ensure ports 3210 and 6791 are available +4. Check network configuration (`nginx-bridge`) + +--- + +## Contributing + +This is a personal monorepo template. Feel free to fork and adapt for your needs. + +### Code Style + +- Single quotes, trailing commas +- 80 character line width +- ESLint + Prettier enforced +- Import order: Types → React → Next → Third-party → @gib → Local + +Run `bun lint:fix` and `bun format:fix` before committing. + +--- + +## License + +MIT + +--- + +## Acknowledgments + +Built with inspiration from: + +- [T3 Turbo](https://github.com/t3-oss/create-t3-turbo) +- [Convex](https://convex.dev) +- [shadcn/ui](https://ui.shadcn.com) +- [Turborepo](https://turbo.build)