18 KiB
Grey Haven Project Setup Checklist
Comprehensive checklist for scaffolding new Grey Haven projects with TanStack Start, FastAPI, or both.
Pre-Project Planning
-
Define project scope (MVP features, future roadmap)
-
Choose architecture:
- TanStack Start only (frontend + BFF)
- FastAPI only (backend API)
- Full-stack (TanStack Start + FastAPI)
- Monorepo or separate repos
-
Define multi-tenant strategy:
- Single-tenant (one customer)
- Multi-tenant (multiple customers)
- Tenant isolation: subdomain, custom domain, path-based
-
Plan authentication:
- Better Auth (recommended for Grey Haven)
- OAuth providers (Google, GitHub, etc.)
- Email/password
- Magic links
-
Database choice:
- PostgreSQL (recommended for Grey Haven)
- MySQL
- SQLite (development only)
-
Hosting platform:
- Vercel (TanStack Start)
- Railway (FastAPI)
- AWS (ECS, Lambda)
- Self-hosted
Repository Setup
Initialize Git
-
Create repository (GitHub, GitLab, Bitbucket)
-
Initialize git:
git init -
Add .gitignore:
node_modules/ .env .env.local __pycache__/ *.pyc .venv/ dist/ .output/ .vercel/ .DS_Store -
Initial commit:
git commit -m "Initial commit" -
Create dev branch:
git checkout -b dev -
Set up branch protection (require PRs for main)
Project Structure
-
Create standard directories:
. ├── .claude/ # Claude Code config (optional) ├── apps/ # Monorepo applications │ ├── web/ # TanStack Start app │ └── api/ # FastAPI app ├── packages/ # Shared packages (monorepo) │ ├── shared-types/ # TypeScript types │ ├── ui/ # Shared UI components │ └── utils/ # Shared utilities ├── docs/ # Documentation ├── scripts/ # Automation scripts └── .github/ # GitHub workflows └── workflows/ -
Add README.md with:
- Project description
- Tech stack
- Getting started guide
- Environment variables
- Deployment instructions
TanStack Start Setup
Installation
-
Create Vite project:
npm create vite@latest my-app -- --template react-ts cd my-app -
Install TanStack Start:
npm install @tanstack/react-router @tanstack/react-query npm install -D @tanstack/router-vite-plugin @tanstack/router-devtools -
Install dependencies:
npm install zod drizzle-orm @better-auth/react npm install -D drizzle-kit tailwindcss postcss autoprefixer
Configure Vite
- Update vite.config.ts:
import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { TanStackRouterVite } from '@tanstack/router-vite-plugin' export default defineConfig({ plugins: [ react(), TanStackRouterVite() ], server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true } } } })
Configure TailwindCSS
-
Initialize Tailwind:
npx tailwindcss init -p -
Update tailwind.config.js:
export default { content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], theme: { extend: {}, }, plugins: [], } -
Add Tailwind directives to
src/index.css:@tailwind base; @tailwind components; @tailwind utilities;
Setup Drizzle (Database)
-
Install Drizzle:
npm install drizzle-orm postgres npm install -D drizzle-kit -
Create drizzle.config.ts:
import { defineConfig } from 'drizzle-kit' export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL! } }) -
Create schema file
src/db/schema.ts:import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core' export const users = pgTable('users', { id: uuid('id').defaultRandom().primaryKey(), email: text('email').notNull().unique(), name: text('name').notNull(), tenantId: uuid('tenant_id').notNull(), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }) export const tenants = pgTable('tenants', { id: uuid('id').defaultRandom().primaryKey(), name: text('name').notNull(), slug: text('slug').notNull().unique(), createdAt: timestamp('created_at').defaultNow() }) -
Create migration:
npx drizzle-kit generate -
Run migration:
npx drizzle-kit migrate
Setup Better Auth
-
Install Better Auth:
npm install better-auth @better-auth/react -
Create auth config
src/lib/auth.ts:import { betterAuth } from 'better-auth' import { drizzleAdapter } from 'better-auth/adapters/drizzle' export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg' }), emailAndPassword: { enabled: true }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! } } }) -
Create auth context for React
-
Add protected route wrapper
Router Setup
- Create routes directory
src/routes/ - Create index route
src/routes/index.tsx - Create auth routes (login, register, logout)
- Create protected routes (dashboard, settings)
- Configure router in
src/main.tsx
Environment Variables
-
Create .env.local:
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32 GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret VITE_API_URL=http://localhost:8000 -
Create .env.example (without secrets)
-
Add to .gitignore:
.env.local
FastAPI Setup
Installation
-
Create project directory:
mkdir my-api && cd my-api -
Setup Python virtual environment:
python -m venv .venv source .venv/bin/activate # or .venv\Scripts\activate on Windows -
Install FastAPI and dependencies:
pip install fastapi uvicorn sqlmodel psycopg2-binary pydantic python-dotenv pip install pytest pytest-asyncio httpx # Testing -
Create requirements.txt:
pip freeze > requirements.txt
Project Structure
- Create standard structure:
my-api/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI app │ ├── models/ # SQLModel models │ │ ├── __init__.py │ │ ├── user.py │ │ └── tenant.py │ ├── repositories/ # Data access layer │ │ ├── __init__.py │ │ ├── base.py │ │ └── user_repository.py │ ├── services/ # Business logic │ │ ├── __init__.py │ │ └── user_service.py │ ├── api/ # API routes │ │ ├── __init__.py │ │ ├── deps.py # Dependencies │ │ └── v1/ │ │ ├── __init__.py │ │ ├── users.py │ │ └── auth.py │ ├── core/ # Config, security │ │ ├── __init__.py │ │ ├── config.py │ │ └── security.py │ └── db/ # Database │ ├── __init__.py │ └── session.py ├── tests/ │ ├── __init__.py │ ├── conftest.py │ └── test_users.py ├── alembic/ # Migrations ├── .env └── requirements.txt
Database Setup (SQLModel)
-
Create SQLModel models
app/models/user.py:from sqlmodel import SQLModel, Field from uuid import UUID, uuid4 from datetime import datetime class User(SQLModel, table=True): __tablename__ = "users" id: UUID = Field(default_factory=uuid4, primary_key=True) email: str = Field(unique=True, index=True) name: str tenant_id: UUID = Field(foreign_key="tenants.id", index=True) created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) -
Create database session
app/db/session.py:from sqlmodel import create_engine, Session from app.core.config import settings engine = create_engine(settings.DATABASE_URL, echo=True) def get_session(): with Session(engine) as session: yield session -
Create tables:
SQLModel.metadata.create_all(engine)
Repository Pattern
-
Create base repository
app/repositories/base.py:from typing import Generic, TypeVar, Type, Optional, List from sqlmodel import Session, select from uuid import UUID ModelType = TypeVar("ModelType", bound=SQLModel) class BaseRepository(Generic[ModelType]): def __init__(self, model: Type[ModelType], session: Session): self.model = model self.session = session def get(self, id: UUID) -> Optional[ModelType]: return self.session.get(self.model, id) def get_all(self, tenant_id: UUID) -> List[ModelType]: statement = select(self.model).where( self.model.tenant_id == tenant_id ) return self.session.exec(statement).all() def create(self, obj: ModelType) -> ModelType: self.session.add(obj) self.session.commit() self.session.refresh(obj) return obj -
Create specific repositories (UserRepository, TenantRepository)
API Routes
-
Create main app
app/main.py:from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api.v1 import users, auth app = FastAPI(title="My API", version="1.0.0") app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"]) app.include_router(users.router, prefix="/api/v1/users", tags=["users"]) @app.get("/health") def health_check(): return {"status": "healthy"} -
Create route handlers
app/api/v1/users.py -
Add dependency injection for tenant_id, user auth
Environment Variables
-
Create .env:
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb SECRET_KEY=generate-with-openssl-rand-hex-32 ALGORITHM=HS256 ACCESS_TOKEN_EXPIRE_MINUTES=30 CORS_ORIGINS=http://localhost:3000 -
Create config
app/core/config.py:from pydantic_settings import BaseSettings class Settings(BaseSettings): DATABASE_URL: str SECRET_KEY: str ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 class Config: env_file = ".env" settings = Settings()
Testing Setup
TypeScript/Vitest (Frontend)
-
Install Vitest:
npm install -D vitest @vitest/ui @testing-library/react @testing-library/jest-dom -
Create vitest.config.ts:
import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts' } }) -
Add test script to package.json:
"test": "vitest" -
Create sample test
src/test/example.test.ts
Pytest (Backend)
-
Create conftest.py with test fixtures:
import pytest from fastapi.testclient import TestClient from sqlmodel import Session, create_engine, SQLModel from app.main import app from app.db.session import get_session @pytest.fixture(name="session") def session_fixture(): engine = create_engine("sqlite:///:memory:") SQLModel.metadata.create_all(engine) with Session(engine) as session: yield session @pytest.fixture(name="client") def client_fixture(session: Session): def get_session_override(): return session app.dependency_overrides[get_session] = get_session_override client = TestClient(app) yield client app.dependency_overrides.clear() -
Add test script:
pytest tests/ -v -
Create sample test
tests/test_users.py
Linting & Formatting
TypeScript (ESLint + Prettier)
-
Install ESLint:
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin -
Install Prettier:
npm install -D prettier eslint-config-prettier -
Create .eslintrc.json
-
Create .prettierrc
-
Add scripts to package.json:
"lint": "eslint src --ext ts,tsx", "format": "prettier --write src"
Python (Ruff + Black)
-
Install Ruff and Black:
pip install ruff black -
Create pyproject.toml:
[tool.black] line-length = 100 [tool.ruff] line-length = 100 select = ["E", "F", "I"] -
Add to requirements.txt (dev dependencies)
CI/CD Setup
GitHub Actions
-
Create workflow
.github/workflows/ci.yml:name: CI on: push: branches: [main, dev] pull_request: branches: [main, dev] jobs: test-frontend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '20' - run: npm ci - run: npm run lint - run: npm test - run: npm run build test-backend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.11' - run: pip install -r requirements.txt - run: pytest tests/ -v -
Add deployment workflow (Vercel, Railway, AWS)
Database Migrations
-
Setup Drizzle migrations (TanStack Start):
npx drizzle-kit generateto create migrationsnpx drizzle-kit migrateto apply migrations
-
Setup Alembic (FastAPI):
pip install alembic alembic init alembic- Configure alembic.ini with DATABASE_URL
- Create migration:
alembic revision --autogenerate -m "message" - Apply migration:
alembic upgrade head
Secrets Management
-
Choose secrets manager:
- Doppler (recommended for Grey Haven)
- AWS Secrets Manager
- Environment variables (for local dev only)
-
Install Doppler CLI (if using Doppler):
# Install: https://docs.doppler.com/docs/install-cli doppler login doppler setup -
Never commit secrets to git
-
Use .env.example for documentation
Deployment
Vercel (TanStack Start)
- Install Vercel CLI:
npm i -g vercel - Connect to Vercel:
vercel link - Configure environment variables in Vercel dashboard
- Deploy:
vercel --prod - Setup custom domain (if needed)
Railway (FastAPI)
- Install Railway CLI:
npm i -g @railway/cli - Login:
railway login - Initialize:
railway init - Add environment variables:
railway variables - Deploy:
railway up
Multi-Tenant Configuration
-
Add tenant_id to all relevant tables
-
Create RLS policies (if using PostgreSQL RLS)
-
Repository pattern enforces tenant filtering
-
Subdomain routing (if applicable):
- tenant1.myapp.com → tenant_id = uuid1
- tenant2.myapp.com → tenant_id = uuid2
-
Tenant signup flow:
- Create tenant record
- Create owner user
- Associate user with tenant
- Generate invitation links
Monitoring & Observability
- Setup error tracking (Sentry, Datadog)
- Add structured logging (Pino for Node, structlog for Python)
- Setup metrics (Prometheus, Datadog)
- Create health check endpoint (
/health) - Setup uptime monitoring (Pingdom, UptimeRobot)
Documentation
- README.md with setup instructions
- API documentation (auto-generated with FastAPI
/docs) - Architecture diagram (optional, but recommended)
- Environment variables documented in .env.example
- Contributing guide (if open-source or team project)
Scoring
- 80+ items checked: Excellent - Production-ready setup ✅
- 60-79 items: Good - Most setup complete ⚠️
- 40-59 items: Fair - Missing important pieces 🔴
- <40 items: Poor - Not ready for development ❌
Priority Items
Complete these first:
- Repository setup - Git, structure, README
- Database schema - Models, migrations
- Authentication - Better Auth, protected routes
- Testing setup - Vitest, pytest
- CI/CD - GitHub Actions, deployment
Common Pitfalls
❌ Don't:
- Commit .env files (use .env.example instead)
- Skip testing setup (add it from day one)
- Ignore linting (consistent code quality matters)
- Deploy without health checks
- Skip multi-tenant isolation (add tenant_id early)
✅ Do:
- Use repository pattern for data access
- Set up CI/CD early (automate testing)
- Document environment variables
- Test authentication thoroughly
- Plan for scale (database indexes, caching)
Related Resources
- TanStack Start Documentation
- FastAPI Documentation
- Better Auth Documentation
- Drizzle ORM Documentation
- project-scaffolding skill
Total Items: 130+ setup checks Critical Items: Repository, Database, Auth, Testing, Deployment Coverage: TanStack Start, FastAPI, Multi-tenant, Testing, CI/CD Last Updated: 2025-11-10