Local Development
Full-stack debugging of the FastAPI backend and React frontend simultaneously in VS Code.
Architecture
Section titled “Architecture”Browser (localhost:5173) ↓ /api/* requestsVite dev server (proxy) ↓ forwards toFastAPI / uvicorn (localhost:8000) ↓ reads/writesAWS DynamoDB (real data, your AWS credentials)DEV_MODE=true is set automatically by the launch config — this bypasses the CloudFront x-origin-token check so the API accepts requests from your local browser.
Prerequisites
Section titled “Prerequisites”- uv installed (
curl -LsSf https://astral.sh/uv/install.sh | sh) - Node.js installed
- AWS credentials configured (
aws configureor~/.aws/credentials)
First-time: point local dev at AWS
Section titled “First-time: point local dev at AWS”.vscode/launch.json sets only ENV and DEV_MODE, and loads everything else from a
.env file in the repo root (envFile). That file is gitignored — create it yourself.
The app defaults to self-hosted (SQLite, local files, env secrets), so a local session aimed at the dev AWS stack has to say so explicitly:
# .env — repo root, never committedDB_BACKEND=dynamodbSTORAGE_BACKEND=s3SECRETS_PROVIDER=ssm
AWS_REGION=eu-west-1S3_BUCKET=<your-dev-bucket># (CLOUDFRONT_URL is no longer used by the app — media URLs are relative)USERS_TABLE=boardsite-users-devGAMES_TABLE=boardsite-games-devRESULTS_TABLE=boardsite-results-devPOSTS_TABLE=boardsite-posts-devRECS_TABLE=boardsite-recs-devREACTIONS_TABLE=boardsite-reactions-devNOTIFICATIONS_TABLE=boardsite-notifications-devSETTINGS_TABLE=boardsite-settings-devWithout the first three, F5 starts against an embedded SQLite file instead of DynamoDB
and fails on the missing /data directory.
First-time: create an admin user
Section titled “First-time: create an admin user”cd apiDB_BACKEND=dynamodb uv run python3 scripts/create-user.py \ --username admin \ --display-name "Admin" \ --role admin \ --password yourpasswordThe DB_BACKEND=dynamodb prefix is what sends the script to AWS rather than a local
SQLite file. The task create-user / task users targets set it for you.
Starting the Debug Session
Section titled “Starting the Debug Session”- Open the repo root in VS Code.
- Press F5.
- Select “Full Stack” from the dropdown.
VS Code will:
- Run
uv syncinapi/(installs/updates Python deps) - Run
npm installinweb/(installs/updates Node deps) - Start FastAPI on
localhost:8000with the Python debugger attached - Start Vite on
localhost:5173with the Node debugger attached - Open Chrome at
http://localhost:5173once Vite is ready
Both processes stop together when you press the red square or close VS Code.
Setting Breakpoints
Section titled “Setting Breakpoints”Python (FastAPI backend)
Section titled “Python (FastAPI backend)”Open any file in api/routes/ or api/lib/ and click in the gutter (left margin) to set a breakpoint.
Example: trace a login request
- Open
api/routes/auth.py - Set a breakpoint on the
get_user(body.username)line insidelogin - In the browser, submit the login form
- VS Code pauses — hover over
bodyto inspectusernameandpassword - Step over (
F10) to watch the password hash comparison - Resume (
F5) to let the response complete
Example: debug a database write
- Open
api/routes/results.py - Set a breakpoint on the
put_result(result)call increate_result - Log a result in the browser
- Inspect the
resultdict before it’s written — catches type errors and missing fields early
TypeScript (React frontend)
Section titled “TypeScript (React frontend)”Open any file in web/src/ and set a breakpoint. VS Code’s Chrome debugger maps source files via sourcemaps.
Example: inspect API response data
- Open
web/src/hooks/useGames.ts - Set a breakpoint inside the
useGamesquery’squeryFn - Navigate to the Catalog page in the browser
- VS Code pauses — step out to see the resolved data
Example: trace a form submission
- Open
web/src/components/LogResultDialog.tsx - Set a breakpoint on the
mutate(...)call - Submit the form
- Inspect the payload before it’s sent to the API
Running Tests
Section titled “Running Tests”Tests use an in-memory FakeTable (api/tests/conftest.py) standing in for the database — no real AWS calls, no data contamination.
cd apiuv run pytest tests/ -v # all tests (takes a few minutes)uv run pytest tests/ -v -k auth # filter by nameuv run pytest tests/test_routes_results.py -v # single fileCommon Workflows
Section titled “Common Workflows”Add a new API route and test it locally
Section titled “Add a new API route and test it locally”- Create
api/routes/myroute.py - Register it in
api/main.py:app.include_router(myroute.router, prefix="/api/myroute") - Press F5 — uvicorn
--reloadpicks up the change automatically (no restart needed) - Test via browser or curl:
Terminal window curl http://localhost:8000/api/myroute
Inspect what’s in DynamoDB
Section titled “Inspect what’s in DynamoDB”cd apiDB_BACKEND=dynamodb uv run python3 -c "from lib.db.results import list_resultsimport jsonprint(json.dumps(list_results(), indent=2, default=str))"Without DB_BACKEND=dynamodb the snippet opens a local SQLite file instead — see the .env note above.
Add a Python dependency
Section titled “Add a Python dependency”cd apiuv add some-package # adds to pyproject.toml and syncs .venvThen commit both pyproject.toml and uv.lock.
Note: Also add the package to
requirements.txt(used bybuild.shfor the Lambda zip) and torequirements-selfhost.txt(used by theDockerfile; it isrequirements.txtminusboto3/mangumplusuvicorn, andtests/test_requirements_sync.pyfails if the two drift).pyproject.tomlis local dev only.
Troubleshooting
Section titled “Troubleshooting”F5 does nothing / wrong config selected Select the “Full Stack” compound from the Run & Debug dropdown (the play button in the sidebar), not a single config.
uv sync fails in preLaunchTask
Run manually: cd api && uv sync. If it errors, check Python version: uv python list.
Backend starts but login returns 503 The app failed to fetch secrets from SSM at startup. Check:
- AWS credentials:
aws sts get-caller-identity AWS_REGIONin.envmatches where the stack is deployed- SSM params exist:
aws ssm get-parameter --name /boardsite/jwt-secret --region eu-west-1
Vite proxy returns 502 / ECONNREFUSED FastAPI isn’t running yet. Check the “Debug Backend” terminal tab in VS Code for startup errors.
Breakpoint not hit in Python
--reload mode spawns a child process. The launch config has "subProcess": true which should handle this. If breakpoints still don’t hit, stop and restart the debug session.
Chrome doesn’t open automatically
Vite’s serverReadyAction watches for the Local: URL in the terminal output. If it doesn’t trigger, open http://localhost:5173 manually.