Running Both Locally
IntermediateTwo processes, a database and a proxy decision. The goal is that a new developer is productive in one command, and that local behaviour predicts production.
Overview
Local development with two deployables is where a project either stays easy to join or becomes a page of setup instructions nobody can follow. The decisions are small but compounding: whether the frontend calls the API directly or through a proxy, whether the database runs in Docker or in the cloud, and how seed data gets there. The one to think about hardest is the proxy, because calling the API directly means every local request is cross-origin while production may not be — so you debug CORS locally that does not exist in production, or worse, the reverse.
Two Processes
The minimum setup, and making it one command.
# Terminal 1 — API
cd tool-hub-api && uvicorn app.main:app --reload --port 8000
# Terminal 2 — web
cd ToolHub && npm run dev # :3000
# One command instead, with concurrently or a Makefile
"dev": "concurrently -n api,web -c blue,green \
\"cd ../api && uvicorn app.main:app --reload\" \"next dev\""
# docker compose, when the stack has more moving parts
services:
db: { image: postgres:16, ports: ["5432:5432"],
environment: { POSTGRES_PASSWORD: dev } }
redis: { image: redis:7, ports: ["6379:6379"] }
api: { build: ./api, ports: ["8000:8000"], depends_on: [db, redis],
volumes: ["./api:/app"] } # bind mount for hot reload
# Run infrastructure in Docker and the apps natively: you get fast
# reloads and a real debugger, without installing Postgres by hand.
# The target is a README with three lines:
# cp .env.example .env.local
# docker compose up -d
# npm run devProxy or Direct
The decision that determines whether local matches production.
// DIRECT — the browser calls the API's origin
NEXT_PUBLIC_API_URL=http://localhost:8000
// + one less moving part, and the network tab shows the real URL
// - every call is cross-origin, so CORS must be configured for
// localhost even if production is same-origin
// - cookies are cross-site locally; SameSite behaves differently
// PROXY — same-origin locally, and optionally in production too
// next.config.js
async rewrites() {
return [{ source: '/api/:path*',
destination: `${process.env.API_URL}/:path*` }]
}
NEXT_PUBLIC_API_URL=/api
// + no CORS at all, cookies are first-party, one origin
// - an extra hop, and the network tab shows your own domain
// Pick the one that MATCHES PRODUCTION. If production serves the API
// from api.aicancode.org, develop cross-origin so you meet CORS and
// cookie problems locally rather than at deploy time. If production
// proxies, proxy locally.
// The worst outcome is a mismatch: a cookie that works locally
// because both sides are "localhost" and silently fails in
// production because two real domains are cross-site.Data and Parity
Seeds, migrations, and the differences that matter.
# Migrations belong in version control and run on deploy
alembic revision --autogenerate -m "add job status"
alembic upgrade head
# Keep a single head — a merge that produces two is a deploy failure
# waiting to happen.
# A seed script, not a database dump
python scripts/seed_dev.py # a known admin, a few problems,
# one of every edge case you have hit
# Never copy production data to a laptop. It is other people's
# personal information, and it removes any reason to keep the seed
# realistic. Anonymise if you must have volume.
# Differences to be aware of, because each has caused a production-only bug:
# local Postgres vs managed (Neon) — extensions, pooling, SSL
# filesystem writes that vanish on serverless
# no HTTPS locally, so Secure cookies behave differently
# fast local latency hiding a race that appears at 200ms
# case-sensitive filesystems on Linux, case-insensitive on Windows/macOS
# -> import './Button' resolves locally and 404s in CI
# Staging exists for what local cannot show: real domains, real TLS,
# real cold starts, real CORS. Deploy there before production.Key Points to Remember
- 1Run infrastructure in Docker and the apps natively for fast reloads and a usable debugger
- 2Choose proxy or direct to match production — a mismatch hides cookie and CORS failures until deploy
- 3Cookies on localhost are same-site even across ports, which is why cookie auth can pass locally and fail in production
- 4Use a seed script rather than production data, and keep migrations single-headed and in version control
- 5Case-sensitive filesystems, serverless writes and real latency are the differences that produce production-only bugs
Interview Questions
Sign in to ask AriaWhat are the trade-offs between proxying the API in development and calling it directly?
Why should local development mirror production's origin setup?
Name a bug that can only appear once deployed, not on localhost.
Ask Aria about Running Both Locally
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.