Building Complex Web Apps with Claude Code, React, and FastAPI
20 Dec 2025Transform Claude from a code assistant into your autonomous development team.
Gone are the days of copy-pasting code snippets from AI chatbots. Claude Code represents a paradigm shift: an autonomous coding agent that reads your codebase, runs commands, manages git, and writes production-ready code—all while respecting the guardrails you define.
But here’s the thing: Claude Code out-of-the-box is powerful. Claude Code with proper setup? That’s a different beast entirely.
This guide walks you through building a production-grade full-stack application (React + FastAPI + MySQL) with a comprehensive Claude Code configuration. You’ll learn to implement:
- MCP servers for real-time type checking and documentation lookup
- Hooks that catch bugs before they’re committed
- Skills that encode your team’s patterns and standards
- Multi-agent architectures that optimize for both cost and quality
Let’s build something that actually works.
The Stack
Before we dive in, here’s what we’re working with:
| Layer | Technology |
|---|---|
| Backend | Python 3.11+, FastAPI (async), SQLAlchemy 2.0 |
| Frontend | React 18, TypeScript (strict), Vite, Tailwind CSS |
| Database | MySQL 8.0 |
| Infrastructure | Docker Compose, Nginx, Celery + Redis |
| Testing | pytest (backend), Playwright (E2E) |
1. Project Foundation
Docker Compose: Your Development Stack
Everything runs in containers. This gives Claude Code a predictable environment to work with.
graph TB
subgraph "Development Stack"
nginx[Nginx :80]
frontend[React Frontend :5173]
backend[FastAPI Backend :8000]
mysql[(MySQL :3306)]
redis[(Redis :6379)]
celery[Celery Worker]
beat[Celery Beat]
end
nginx --> frontend
nginx --> backend
backend --> mysql
backend --> redis
celery --> redis
celery --> mysql
beat --> celery
A minimal docker-compose.yml structure:
services:
mysql:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
backend:
build: ./backend
volumes:
- ./backend:/app
depends_on:
mysql:
condition: service_healthy
frontend:
build: ./frontend
volumes:
- ./frontend:/app
- /app/node_modules # Exclude node_modules from mount
command: npm run dev -- --host
Key insight: Health checks matter. Claude Code will attempt to run tests and queries—if services aren’t ready, you’ll get confusing failures.
CLAUDE.md: Your Project’s Constitution
The CLAUDE.md file is auto-loaded into every Claude Code session. Think of it as onboarding documentation for your AI teammate.
# Project Name - AI Assistant Guide
## Critical Rules
1. **Read before edit** - Always use Read tool first
2. **Models require:** `__table_args__ = {'extend_existing': True}`
3. **DB naming:** Tables=PascalCase, Columns=camelCase
4. **Money:** Use `Decimal`, never `float`
5. **Auth:** HttpOnly cookies (not localStorage)
6. **Quality:** Run `make quality` after every change
## Essential Commands
| Command | Purpose |
|---------|---------|
| `make up` | Start all services |
| `make quality` | Lint + typecheck + tests |
| `make test-backend` | Run pytest |
| `make test-e2e` | Run Playwright tests |
## MCP Tools (Use These)
| Tool | When to Use |
|------|-------------|
| `mcp__language-server-python__diagnostics` | Before/after Python edits |
| `mcp__language-server-typescript__diagnostics` | Before/after TypeScript edits |
| `mcp__context7__*` | Library documentation lookup |
Keep it concise. As Anthropic’s best practices note: “A single file that’s hundreds of lines long should trigger a review; Claude can always look at additional details on demand.”
2. MCP Server Integration
MCP (Model Context Protocol) servers extend Claude’s capabilities with real-time tools. Here’s the configuration that makes everything work.
The .mcp.json Configuration
{
"mcpServers": {
"context7": {
"type": "http",
"url": "https://mcp.context7.com/mcp"
},
"language-server-typescript": {
"type": "stdio",
"command": "/path/to/mcp-language-server",
"args": [
"--workspace", "/path/to/project/",
"--lsp", "/usr/local/bin/typescript-language-server",
"--", "--stdio"
]
},
"language-server-python": {
"type": "stdio",
"command": "/path/to/mcp-language-server",
"args": [
"--workspace", "/path/to/project/",
"--lsp", "/usr/local/bin/pyright-langserver",
"--", "--stdio"
]
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest", "--browser", "chromium"]
},
"mysql-db": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@f4ww4z/mcp-mysql-server",
"mysql://user:pass@localhost:3306/database"]
}
}
}
Language Servers: Your Type Safety Net
These are game-changers. Instead of Claude guessing at types and hoping for the best, it can:
# Before ANY code change:
mcp__language-server-python__diagnostics -> Check existing errors
# After EVERY code change:
mcp__language-server-python__diagnostics -> Catch new errors immediately
This catches issues during the coding session, not after a failed build.
Context7: No More Hallucinated APIs
Claude’s training data has a cutoff. Context7 solves this by fetching live documentation:
# Step 1: Find the library
mcp__context7__resolve-library-id("fastapi")
# Step 2: Get current docs
mcp__context7__get-library-docs(library_id, topic="dependency injection")
This is crucial for frameworks that evolve quickly. No more from fastapi import Depends when the API changed six months ago.
Database MCP: Schema at Your Fingertips
Instead of Claude reading model files and inferring relationships:
# Direct schema inspection
mcp__mysql-db__describe_table("User")
mcp__mysql-db__list_tables()
# Verify data during debugging
mcp__mysql-db__query("SELECT COUNT(*) FROM Reservation WHERE status = 'pending'")
This is faster and more accurate than parsing Python models.
3. Hooks: Your Automated Guardrails
Hooks intercept Claude’s actions and enforce quality gates. They’re the difference between “it works on my machine” and “it works.”
graph LR
A[Claude Edits File] --> B{Post-Edit Hook}
B -->|Lint Check| C{Pass?}
C -->|Yes| D[Continue]
C -->|No| E[Feedback to Claude]
E --> F[Claude Fixes]
F --> A
G[Claude Commits] --> H{Pre-Commit Hook}
H -->|.env check| I{Pass?}
I -->|Yes| J[Commit Allowed]
I -->|No| K[Block + Feedback]
Post-Edit Hook: Instant Quality Feedback
This hook runs after every file edit, providing immediate feedback:
#!/bin/bash
# .claude/scripts/post-edit-hook.sh
HOOK_DATA=$(cat)
FILE_PATH=$(echo "$HOOK_DATA" | jq -r '.tool_input.file_path')
ERRORS=()
WARNINGS=()
# Python files: Run ruff + black
if [[ "$FILE_PATH" =~ \.py$ ]]; then
if ! docker exec backend poetry run ruff check "$REL_PATH" --quiet; then
ERRORS+=("Ruff lint errors detected")
fi
if ! docker exec backend poetry run black "$REL_PATH" --check --quiet; then
WARNINGS+=("Formatting needed - run: poetry run black $REL_PATH")
fi
fi
# TypeScript files: Run tsc + eslint
if [[ "$FILE_PATH" =~ \.(ts|tsx)$ ]]; then
TSC_OUTPUT=$(docker exec frontend npx tsc --noEmit 2>&1 || true)
if echo "$TSC_OUTPUT" | grep -q "error TS"; then
ERRORS+=("TypeScript errors detected")
fi
fi
# Return structured JSON feedback
cat <<EOF
{
"decision": "approve",
"feedback": {
"errors": $(printf '%s\n' "${ERRORS[@]}" | jq -R -s 'split("\n")'),
"warnings": $(printf '%s\n' "${WARNINGS[@]}" | jq -R -s 'split("\n")')
}
}
EOF
Key pattern: Always return "decision": "approve" with feedback. Blocking edits is frustrating; providing feedback lets Claude self-correct.
Pre-Commit Hook: The Final Gatekeeper
This hook runs before git commit commands:
#!/bin/bash
# .claude/scripts/pre-commit-hook.sh
HOOK_DATA=$(cat)
COMMAND=$(echo "$HOOK_DATA" | jq -r '.tool_input.command')
# Only check actual commits
if ! echo "$COMMAND" | grep -q "git commit"; then
echo '{"decision": "approve"}'
exit 0
fi
# BLOCK: .env files
STAGED_ENV=$(git diff --cached --name-only | grep -E '^\.env')
if [[ -n "$STAGED_ENV" ]]; then
cat <<EOF
{
"decision": "block",
"reason": "Cannot commit .env files",
"feedback": {"suggestion": "Remove sensitive files from staging"}
}
EOF
exit 0
fi
# WARN: Non-conventional commit messages
COMMIT_MSG=$(echo "$COMMAND" | grep -oP '(?<=-m ")[^"]+')
if ! echo "$COMMIT_MSG" | grep -qE '^(feat|fix|docs|refactor):'; then
echo '{"decision": "approve", "feedback": {"warnings": ["Non-conventional commit message"]}}'
exit 0
fi
echo '{"decision": "approve"}'
Registering Hooks
In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": ".claude/scripts/post-edit-hook.sh",
"timeout": 60000
}]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": ".claude/scripts/pre-commit-hook.sh",
"timeout": 120000
}]
}
]
}
}
4. Skills: Domain Knowledge Encapsulation
Skills transform Claude from a general-purpose assistant into a specialized developer who knows your codebase.
Skill Anatomy
skill-name/
├── SKILL.md # Required: Instructions + metadata
├── references/ # Optional: Detailed documentation
│ └── patterns.md
└── scripts/ # Optional: Executable helpers
└── generate.py
The SKILL.md file has two parts:
---
name: my-backend
description: |
FastAPI/SQLAlchemy patterns for this project. Use when working on
backend/ files: endpoints, services, models, schemas. Loads anti-pattern
rules automatically.
---
# Backend Development
## Model Pattern
```python
class User(Base):
__tablename__ = "User"
__table_args__ = {'extend_existing': True} # REQUIRED
id: Mapped[str] = mapped_column(String(36), primary_key=True)
Anti-Pattern Checklist
- No HTTPException in services - use DomainError
- Money uses Decimal, never float
- All DB operations are async ```
Critical: The description field in frontmatter is the trigger. Claude reads this to decide when to load the skill. Be explicit about when it should activate.
Progressive Disclosure
Skills use a three-level loading system to manage context:
graph TD
A[Level 1: Metadata] -->|Always loaded| B[~100 words]
C[Level 2: SKILL.md body] -->|When triggered| D[~5000 words max]
E[Level 3: References] -->|As needed| F[Unlimited]
B --> G[Claude decides to use skill]
G --> D
D --> H[Claude needs details]
H --> F
This prevents context window bloat. Keep SKILL.md under 500 lines; move detailed patterns to references/.
Creating Skills with skill-creator
Use the /skill-creator skill to generate new skills:
/skill-creator Create a testing skill that covers pytest patterns,
Playwright E2E setup, and quality gates for our FastAPI + React project
The skill-creator will:
- Ask clarifying questions about your testing patterns
- Generate the skill structure
- Create
SKILL.mdwith proper frontmatter - Suggest reference files for detailed documentation
Essential Skills for Full-Stack Apps
| Skill | Purpose | Triggers On |
|---|---|---|
my-backend |
FastAPI patterns, models, services | backend/*.py files |
my-frontend |
React patterns, hooks, i18n | frontend/src/*.tsx files |
my-testing |
pytest, Playwright, quality gates | Test-related requests |
my-deploy |
SSH, migrations, rollback | Deployment requests |
my-anti-pattern |
Code quality rules | Always loaded via other skills |
5. Multi-Agent Architecture
Here’s where it gets powerful. Instead of one Claude doing everything, you orchestrate multiple agents with different strengths.
Model Strategy
| Model | Use For | Why |
|---|---|---|
| Opus 4.5 | Orchestration, review, complex reasoning | Highest quality, best at catching subtle issues |
| Sonnet 4.5 | Bulk coding, routine implementations | Cost-effective, still excellent for defined tasks |
graph TB
User[User Request] --> Opus[Opus 4.5 Orchestrator]
Opus --> |"Task(model=sonnet)"| S1[Sonnet: Backend]
Opus --> |"Task(model=sonnet)"| S2[Sonnet: Frontend]
Opus --> |"Task(model=sonnet)"| S3[Sonnet: Testing]
S1 --> |Report| Opus
S2 --> |Report| Opus
S3 --> |Report| Opus
Opus --> Review[Opus Reviews & Integrates]
Review --> Response[Final Response]
The Invoke Pattern
Create skills that dispatch subagents with proper context:
---
name: invoke-backend
description: |
INVOKE COMMAND: Use `/invoke-backend <task>` to spawn a Sonnet 4.5
subagent for backend tasks. Automatically loads backend + anti-pattern
skills. Cost-effective for routine tasks.
---
# Invoke Backend Subagent
## Dispatch Template
When invoked, dispatch:
Task( subagent_type=”general-purpose”, model=”sonnet”, prompt=””” You are a backend developer. Complete this task following all patterns.
Task
{user_task}
Required Skills (LOAD FIRST)
- Read
.claude/skills/my-backend/SKILL.md - Read
.claude/skills/my-anti-pattern/SKILL.md
Mandatory MCP Tool Usage
- Before edits:
mcp__language-server-python__diagnostics - After edits:
mcp__language-server-python__diagnostics - Fix ALL type errors immediately
Anti-Pattern Checklist
- No HTTPException in services
- Models have extend_existing = True
- Money uses Decimal
Return Format
| File | Changes | |——|———| | path | description |
Quality: make quality PASS/FAIL
“””
)
### Background Agents for Parallel Work
When tasks are independent, run them in parallel:
```python
# Main agent dispatches multiple subagents
Task(
subagent_type="general-purpose",
model="sonnet",
run_in_background=True, # Don't wait
prompt="Implement the user service..."
)
Task(
subagent_type="general-purpose",
model="sonnet",
run_in_background=True,
prompt="Implement the user API endpoints..."
)
# Later, collect results
TaskOutput(task_id="agent-1", block=True)
TaskOutput(task_id="agent-2", block=True)
When to use:
- Independent features that don’t share files
- Testing while implementation continues
- Code review alongside bug fixes
6. Quality Automation Pipeline
Tie everything together with a Makefile:
.PHONY: quality lint typecheck test-backend test-e2e
quality: lint typecheck test-backend
@echo "All quality checks passed"
lint:
docker exec backend poetry run ruff check app/ --fix
docker exec backend poetry run black app/
docker exec frontend npm run lint
typecheck:
docker exec backend poetry run mypy app/
docker exec frontend npx tsc --noEmit
test-backend:
docker exec backend poetry run pytest -v
test-e2e:
npx playwright test
Playwright Configuration for Reliable E2E Tests
// playwright.config.ts
export default defineConfig({
testDir: './e2e/tests',
fullyParallel: false, // Sequential for state-dependent tests
workers: 1,
retries: process.env.CI ? 2 : 1,
use: {
baseURL: 'http://localhost',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{
name: 'booking-flow',
use: { ...devices['Desktop Firefox'] },
retries: 0, // No retries for deterministic flows
},
],
});
7. Common Pitfalls & Anti-Patterns
Context Window Abuse
Problem: Loading everything into skills, never clearing context.
Solution:
- Use
/clearbetween unrelated tasks - Keep SKILL.md under 500 lines
- Use progressive disclosure—references load on demand
As Anthropic notes: “Every time you start something new, clear the chat. You don’t need all that history eating your tokens.”
Agent Misuse
Problem: Using Opus for routine tasks; not enforcing quality in subagents.
Solution:
- Opus orchestrates, Sonnet implements
- Every subagent prompt includes MCP tool requirements
- Subagents must report
make qualityresults
Guardrail Gaps
Problem: Hooks that only warn, never block; missing secret detection.
Solution:
- Block
.envfiles unconditionally - Block commits with failing type checks
- Log everything to
.claude/logs/for debugging
Skill Design Mistakes
Problem: README files in skills; duplicated information; deep nesting.
Solution:
- Skills are for Claude, not humans—no README needed
- Information lives in SKILL.md OR references, not both
- Keep references one level deep from SKILL.md
8. Advanced Patterns We Haven’t Implemented (Yet)
Git Worktrees for Parallel Development
Run multiple Claude instances on isolated branches:
git worktree add ../project-feature-a feature-a
git worktree add ../project-feature-b feature-b
# Terminal 1: Claude works on feature-a
# Terminal 2: Claude works on feature-b
This enables true parallel development without merge conflicts.
Headless Mode for CI/CD
Integrate Claude Code into your pipeline:
claude -p "Run all tests and fix any failures" \
--output-format stream-json \
--dangerously-skip-permissions
Warning: Only use --dangerously-skip-permissions in isolated containers.
Visual Iteration with Screenshots
Paste UI screenshots directly:
- Take screenshot (Cmd+Ctrl+Shift+4 on macOS)
- Paste into Claude Code (Ctrl+V)
- “Make this match the design”
- Screenshot result, iterate
Results improve significantly after 2-3 cycles.
Memory Snapshots for Session Continuity
Save context before complex sessions:
mkdir -p .claude/memory/snapshots
# Claude can write session state here
# Load on next session to continue work
Extended Thinking for Complex Decisions
Use specific phrases to trigger deeper reasoning:
| Phrase | Thinking Budget |
|---|---|
| “think” | Standard |
| “think hard” | Extended |
| “think harder” | Maximum |
| “ultrathink” | Unlimited |
Example: “Think hard about the best way to implement this payment flow considering our existing architecture.”
Conclusion
You now have a blueprint for turning Claude Code into an autonomous development team:
- Foundation: Docker Compose + CLAUDE.md give Claude a predictable environment
- MCP Servers: Real-time type checking and documentation eliminate hallucinations
- Hooks: Automated guardrails catch issues before they’re committed
- Skills: Domain knowledge makes Claude an expert in your codebase
- Multi-Agent: Opus orchestrates, Sonnet implements—optimized for cost and quality
The key insight? Claude Code isn’t just an assistant—it’s infrastructure. Treat it like you’d treat any other part of your development stack: configure it properly, enforce standards automatically, and iterate on what works.
Start with skills (they’re the highest ROI), add hooks (they prevent regressions), then scale with agents (for parallel work). Your future self will thank you.
Resources
- Claude Code: Best practices for agentic coding - Anthropic’s official guide
- Prompting best practices - Claude documentation
- Context7 MCP Server - Live documentation lookup
Happy building.
Disclaimer: Content is accurate at the time of publication, however updates and new additions happen frequently which could change the accuracy or relevance. Please keep this in mind when using my content as guidelines. Please always test in a testing or development environment, I do not accept any liability for damages caused by this content.
If you liked this post, you can share it with your followers or follow me on Twitter!