Developer Handbook
Complete Development Guide: From setup to deployment, everything you need to develop on the AltSportsLeagues.ai platform.
Table of Contents
- Getting Started
- Development Environment
- Architecture Overview
- Development Workflows
- Testing
- Deployment
- Best Practices
- Troubleshooting
Getting Started
Prerequisites
# Required tools
- Node.js 18+ (for frontend)
- Python 3.11+ (for backend)
- Docker & Docker Compose
- uv (Python package manager)
- Google Cloud SDK (for deployment)
- Vercel CLI (for frontend deployment)Initial Setup
# 1. Clone repository
git clone [repository-url]
cd 2.1-cloud-run-docker-mcp
# 2. Install backend dependencies
cd apps/backend
uv sync
# 3. Install frontend dependencies
cd ../../clients/frontend
npm install
# 4. Install docs dependencies
cd ../docs-site
npm install
# 5. Configure environment variables
cp .env.example .env
# Edit .env with your credentialsEnvironment Variables
Backend (.env)
# AI Services
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Google Cloud
GOOGLE_CLOUD_PROJECT=your-project
FIRESTORE_PROJECT_ID=your-project
# Databases
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_KEY=eyJ...
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=password
# n8n
N8N_API_URL=https://n8n.altsportsleagues.ai
N8N_API_KEY=n8n_api_...Frontend (.env.local)
NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_FIREBASE_API_KEY=...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECT_ID=...Development Environment
Backend Development
Starting the Backend
# Method 1: Direct Python (fastest for development)
cd apps/backend
uv run uvicorn server:app --reload --port 8080
# Method 2: Docker (matches production)
cd apps/backend
./deploy-local-docker.sh
# Access at: http://localhost:8090
# Method 3: Docker Compose (full stack)
docker-compose -f deploy/docker-compose.yml upBackend Structure
apps/backend/
ββ server.py # Main FastAPI application
ββ routers/ # API route handlers
β ββ v1/ # Version 1 API routes
β ββ ...
ββ services/ # Business logic
β ββ intent_classifier.py
β ββ league_data_processor.py
β ββ ...
ββ mcp_servers/ # MCP server implementations
β ββ servers/
ββ models/ # Pydantic models
ββ utils/ # Utility functions
ββ tests/ # Test suite
ββ config/ # ConfigurationAdding a New API Endpoint
-
Create Router Module
# apps/backend/routers/v1/my_feature.py from fastapi import APIRouter from models.my_models import MyRequest, MyResponse router = APIRouter(prefix="/v1/my-feature", tags=["My Feature"]) @router.get("/") async def get_items() -> list[MyResponse]: """Get all items""" return [] @router.post("/") async def create_item(request: MyRequest) -> MyResponse: """Create a new item""" return MyResponse() -
Register Router
# apps/backend/server.py from routers.v1.my_feature import router as my_feature_router app.include_router(my_feature_router) -
Add Tests
# apps/backend/tests/test_my_feature.py def test_get_items(): response = client.get("/v1/my-feature/") assert response.status_code == 200
Frontend Development
Starting the Frontend
cd clients/frontend
npm run dev
# Access at: http://localhost:3031Frontend Structure
clients/frontend/
ββ app/ # Next.js App Router
β ββ (routes)/ # Route groups
β ββ api/ # API routes
β ββ components/ # React components
β ββ layout.tsx # Root layout
ββ components/ # Shared components
β ββ ui/ # shadcn/ui components
ββ lib/ # Utilities
ββ hooks/ # Custom React hooks
ββ store/ # Zustand stores
ββ styles/ # Global styles
ββ types/ # TypeScript types
ββ public/ # Static assetsCreating a New Page
-
Create Route File
// app/(routes)/my-page/page.tsx import { Metadata } from 'next' export const metadata: Metadata = { title: 'My Page', description: 'My page description' } export default function MyPage() { return ( <div> <h1>My Page</h1> </div> ) } -
Add to Navigation
// app/components/navigation.tsx const links = [ { href: '/my-page', label: 'My Page' } ]
Creating a Component
// components/my-component.tsx
'use client'
import { useState } from 'react'
interface MyComponentProps {
title: string
onAction?: () => void
}
export function MyComponent({ title, onAction }: MyComponentProps) {
const [state, setState] = useState(false)
return (
<div>
<h2>{title}</h2>
<button onClick={() => {
setState(!state)
onAction?.()
}}>
Toggle
</button>
</div>
)
}State Management with Zustand
// store/my-store.ts
import { create } from 'zustand'
interface MyStore {
count: number
increment: () => void
decrement: () => void
}
export const useMyStore = create<MyStore>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// Usage in component
import { useMyStore } from '@/store/my-store'
function MyComponent() {
const { count, increment } = useMyStore()
return <button onClick={increment}>{count}</button>
}Documentation Development
Starting Docs Site
cd clients/docs-site
npm run dev
# Access at: http://localhost:3001Adding Documentation
- Create MDX File
// pages/my-topic/my-guide.mdx # My Guide This is a guide about my topic. ## Section 1 Content here... ```typescript // Code example const example = 'code' - Update Navigation
// pages/my-topic/_meta.js export default { 'my-guide': 'My Guide Title', 'other-guide': 'Other Guide' }
Development Workflows
Feature Development Workflow
-
Create Feature Branch
git checkout -b feature/my-feature -
Develop & Test Locally
- Backend: Run tests with
pytest - Frontend: Test in browser
- Integration: Test full flow
- Backend: Run tests with
-
Commit Changes
git add . git commit -m "feat: add my feature" -
Push & Create PR
git push origin feature/my-feature # Create pull request on GitHub
Code Review Checklist
- β Code follows style guidelines
- β Tests pass locally
- β Documentation updated
- β No sensitive data in code
- β TypeScript types are correct
- β Error handling implemented
- β Performance considered
- β Security reviewed
Git Commit Convention
# Format
<type>(<scope>): <subject>
# Types
feat: New feature
fix: Bug fix
docs: Documentation only
style: Code style changes
refactor: Code refactoring
test: Adding tests
chore: Maintenance tasks
# Examples
feat(api): add league discovery endpoint
fix(frontend): resolve navigation bug
docs(readme): update setup instructionsTesting
Backend Testing
Unit Tests
# tests/test_services.py
import pytest
from services.my_service import MyService
def test_my_function():
service = MyService()
result = service.my_function("input")
assert result == "expected"
@pytest.mark.asyncio
async def test_async_function():
service = MyService()
result = await service.async_function()
assert result is not NoneAPI Tests
# tests/test_api.py
from fastapi.testclient import TestClient
from server import app
client = TestClient(app)
def test_get_leagues():
response = client.get("/v1/leagues")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)Running Tests
cd apps/backend
# Run all tests
pytest
# Run specific test file
pytest tests/test_api.py
# Run with coverage
pytest --cov=. --cov-report=html
# Run specific test
pytest tests/test_api.py::test_get_leagues -vFrontend Testing
E2E Tests with Playwright
// e2e/homepage.spec.ts
import { test, expect } from '@playwright/test'
test('homepage loads correctly', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveTitle(/AltSportsLeagues/)
await expect(page.locator('h1')).toBeVisible()
})
test('league search works', async ({ page }) => {
await page.goto('/leagues')
await page.fill('input[name="search"]', 'NBA')
await page.click('button[type="submit"]')
await expect(page.locator('.league-card')).toBeVisible()
})Running E2E Tests
cd clients/frontend
# Run tests
npm run test:e2e
# Run in UI mode
npm run test:e2e:ui
# Run specific test
npx playwright test e2e/homepage.spec.tsIntegration Testing
MCP Tool Testing
# Test MCP tool functionality
import pytest
from mcp_servers.servers.my_mcp_server import MyMCPServer
@pytest.mark.asyncio
async def test_mcp_tool():
server = MyMCPServer()
result = await server.call_tool("my_tool", {"param": "value"})
assert result["success"] is TrueDeployment
Local Deployment (Docker)
Backend Local Deployment
cd apps/backend
# Deploy to local Docker
./deploy-local-docker.sh
# Test deployment
./test-local-deployment.sh
# View logs
docker compose logs -f
# Stop
docker compose downProduction Deployment
Backend to Cloud Run
cd apps/backend
# Deploy to Cloud Run
./deploy-to-cloud-run.sh
# Monitor deployment
gcloud run services describe altsportsleagues-backend \
--region us-central1
# View logs
gcloud run services logs tail altsportsleagues-backend \
--region us-central1Frontend to Vercel
cd clients/frontend
# Deploy to production
vercel --prod
# Check deployment status
vercel inspect [deployment-url]
# View logs
vercel logsDocs to Vercel
cd clients/docs-site
# Build with schema injection
npm run build
# Deploy
vercel --prodDeployment Checklist
- β All tests passing
- β Environment variables configured
- β Docker build successful (backend)
- β Next.js build successful (frontend/docs)
- β No secrets in code
- β API endpoints tested
- β Health checks pass
- β Monitoring configured
- β Documentation updated
Best Practices
Code Quality
TypeScript/JavaScript
// β
Good: Strong typing
interface User {
id: string
name: string
email: string
}
function getUser(id: string): Promise<User> {
return fetch(`/api/users/${id}`).then(r => r.json())
}
// β Bad: Weak typing
function getUser(id: any): Promise<any> {
return fetch(`/api/users/${id}`).then(r => r.json())
}Python
# β
Good: Type hints and validation
from pydantic import BaseModel
class User(BaseModel):
id: str
name: str
email: str
async def get_user(user_id: str) -> User:
# Implementation
pass
# β Bad: No typing
def get_user(user_id):
# Implementation
passError Handling
Frontend
// β
Good: Proper error handling
try {
const data = await fetch('/api/leagues').then(r => r.json())
return data
} catch (error) {
console.error('Failed to fetch leagues:', error)
toast.error('Failed to load leagues. Please try again.')
return []
}
// β Bad: Silent failures
const data = await fetch('/api/leagues').then(r => r.json())
return data || []Backend
# β
Good: Specific error handling
from fastapi import HTTPException
@router.get("/leagues/{league_id}")
async def get_league(league_id: str):
try:
league = await league_service.get(league_id)
if not league:
raise HTTPException(status_code=404, detail="League not found")
return league
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error(f"Failed to get league: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# β Bad: Generic error handling
@router.get("/leagues/{league_id}")
async def get_league(league_id: str):
try:
return await league_service.get(league_id)
except:
return NoneSecurity
Authentication
// β
Good: Protected routes
import { auth } from '@/lib/firebase'
async function protectedAction() {
const user = auth.currentUser
if (!user) {
throw new Error('Not authenticated')
}
const token = await user.getIdToken()
return fetch('/api/protected', {
headers: { Authorization: `Bearer ${token}` }
})
}Input Validation
# β
Good: Validated input
from pydantic import BaseModel, validator
class LeagueCreate(BaseModel):
name: str
sport: str
@validator('name')
def name_must_not_be_empty(cls, v):
if not v.strip():
raise ValueError('Name cannot be empty')
return vPerformance
Frontend Optimization
// β
Good: Memoization
import { useMemo } from 'react'
function LeagueList({ leagues }) {
const sortedLeagues = useMemo(
() => leagues.sort((a, b) => a.name.localeCompare(b.name)),
[leagues]
)
return <div>{/* Render sorted leagues */}</div>
}
// β
Good: Lazy loading
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>Loading...</div>
})Backend Optimization
# β
Good: Async operations
import asyncio
async def fetch_multiple_leagues(league_ids: list[str]):
tasks = [league_service.get(id) for id in league_ids]
return await asyncio.gather(*tasks)
# β
Good: Database connection pooling
from sqlalchemy.pool import QueuePool
engine = create_engine(
DATABASE_URL,
poolclass=QueuePool,
pool_size=10,
max_overflow=20
)Troubleshooting
Common Issues
Backend Issues
Issue: Import errors
# Solution: Install dependencies
cd apps/backend
uv syncIssue: Port already in use
# Solution: Kill process or use different port
lsof -ti:8080 | xargs kill -9
# Or
uvicorn server:app --reload --port 8081Issue: Database connection failed
# Solution: Check environment variables
echo $SUPABASE_URL
echo $NEO4J_URI
# Test connections
python -c "from config import settings; print(settings.SUPABASE_URL)"Frontend Issues
Issue: Module not found
# Solution: Install dependencies
cd clients/frontend
rm -rf node_modules package-lock.json
npm installIssue: Build fails
# Solution: Clear Next.js cache
rm -rf .next
npm run buildIssue: API calls fail
# Solution: Check API URL
# In .env.local:
NEXT_PUBLIC_API_URL=http://localhost:8080
# Verify backend is running
curl http://localhost:8080/healthDebug Tools
Backend Debug
# Add logging
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.debug("Debug message")
logger.info("Info message")
logger.error("Error message")
# Use debugger
import pdb; pdb.set_trace()Frontend Debug
// Browser console
console.log('Debug:', data)
console.table(array)
console.dir(object)
// React DevTools
// Install: https://react.dev/learn/react-developer-tools
// Network tab
// Monitor API calls in browser DevToolsResources
- Platform Guide - Full platform overview
- API Reference - Complete API documentation
- Frontend Guide - Frontend development
- Integration Playbook - Integration patterns
Support
- GitHub Issues: Report bugs and request features
- Documentation: https://docs.altsportsleagues.ai (opens in a new tab)
- Team: Contact for questions and support
Last Updated: November 2024 Version: 2.1.0