Local Development Guide
GitGazer supports flexible local development workflows. You can run the full stack locally (frontend + backend) or point the local frontend at the production API for frontend-only work.
Prerequisites
Before you start, make sure you have:
- Node.js 24+ — use asdf for version management
- pnpm — package manager (monorepo workspaces)
- AWS CLI configured with appropriate permissions
- aws-vault (recommended) for credential management
Hosts File Entry
The frontend dev server runs on app.gitgazer.localhost for proper cookie handling and OAuth redirects. Add this entry to your hosts file:
# macOS / Linux
sudo nano /etc/hosts
# Add this line:
127.0.0.1 app.gitgazer.localhost
# Windows (run as Administrator)
# Edit: C:\Windows\System32\drivers\etc\hosts
# Add: 127.0.0.1 app.gitgazer.localhost
Development Scenarios
Scenario 1: Full Local Development
Use when: You're developing both frontend and backend, or testing API changes locally.
1. Configure the Frontend
Create apps/web/.env.local:
VITE_HOST_URL="https://app.gitgazer.localhost:5173"
VITE_COGNITO_DOMAIN="<COGNITO_DOMAIN>"
VITE_COGNITO_USER_POOL_ID="<USER_POOL_ID>"
VITE_COGNITO_USER_POOL_CLIENT_ID="<USER_POOL_CLIENT_ID>"
VITE_REST_API_ENDPOINT="https://app.gitgazer.localhost:5173/api"
VITE_IMPORT_URL_BASE="https://app.gitgazer.localhost:5173/api/import"
VITE_REST_API_REGION="us-east-1"
VITE_WEBSOCKET_API_ENDPOINT="<WEBSOCKET_ENDPOINT>"
The key setting is VITE_REST_API_ENDPOINT pointing to localhost:5173/api — this activates Vite's proxy to forward API requests to the local backend.
2. Start the Backend
cd apps/api
pnpm install
# Copy and edit environment config
cp .env.dev.example .env
# Edit .env with your AWS configuration
# Start local backend (port 8080)
aws-vault exec <profile> --no-session -- pnpm run dev:api
3. Start the Frontend
In a separate terminal:
cd apps/web
pnpm install
pnpm run dev
4. Open the App
Navigate to https://app.gitgazer.localhost:5173.
You'll see a browser warning about the self-signed SSL certificate. Click Advanced → Proceed to continue. This is expected for local development.
How the Proxy Works
Vite's dev server intercepts requests to /api and forwards them to the local backend:
Browser → https://app.gitgazer.localhost:5173/api/integrations
→ Vite proxy intercepts /api/*
→ http://localhost:8080/api/integrations
→ Local Lambda dev server handles request
→ Response flows back through proxy
Because the frontend and API appear same-origin, cookies work seamlessly with no CORS issues.
Scenario 2: Local Frontend + Production Backend
Use when: You're only developing frontend features and want to use real production data.
1. Configure the Frontend
Create apps/web/.env.local:
VITE_HOST_URL="https://app.gitgazer.localhost:5173"
VITE_COGNITO_DOMAIN="<COGNITO_DOMAIN>"
VITE_COGNITO_USER_POOL_ID="<USER_POOL_ID>"
VITE_COGNITO_USER_POOL_CLIENT_ID="<USER_POOL_CLIENT_ID>"
VITE_REST_API_ENDPOINT="https://<GITGAZER_DOMAIN>/api"
VITE_IMPORT_URL_BASE="https://<GITGAZER_DOMAIN>/v1/api/import/"
VITE_REST_API_REGION="us-east-1"
VITE_WEBSOCKET_API_ENDPOINT="<WEBSOCKET_ENDPOINT>"
The key setting is VITE_REST_API_ENDPOINT pointing to the production domain — this bypasses the Vite proxy entirely.
2. Start the Frontend
cd apps/web
pnpm install
pnpm run dev
No backend setup needed. All API calls go directly to production.
- The production backend's
ALLOWED_FRONTEND_ORIGINSmust includehttps://app.gitgazer.localhost:5173. - The Cognito app client must have
https://app.gitgazer.localhost:5173as an allowed callback URL.
Environment Variables
Frontend Variables
| Variable | Full Local | Hybrid (Prod Backend) | Description |
|---|---|---|---|
VITE_REST_API_ENDPOINT | https://app.gitgazer.localhost:5173/api | https://<DOMAIN>/api | REST API base URL |
VITE_IMPORT_URL_BASE | https://app.gitgazer.localhost:5173/api/import | https://<DOMAIN>/v1/api/import/ | Webhook import base URL |
VITE_HOST_URL | https://app.gitgazer.localhost:5173 | https://app.gitgazer.localhost:5173 | Frontend URL (same in both) |
VITE_COGNITO_DOMAIN | Cognito domain | Cognito domain | Cognito auth domain |
VITE_COGNITO_USER_POOL_ID | Pool ID | Pool ID | Cognito user pool ID |
VITE_COGNITO_USER_POOL_CLIENT_ID | Client ID | Client ID | Cognito app client ID |
VITE_WEBSOCKET_API_ENDPOINT | WS endpoint | WS endpoint | WebSocket API endpoint |
Backend Variables
Backend variables are configured in apps/api/.env. Copy from .env.dev.example and fill in your AWS configuration. Key variables:
| Variable | Description |
|---|---|
AWS_REGION | AWS region for all services |
RDS_HOST | Database connection host |
RDS_HOSTNAME | Real hostname for IAM token signing |
RDS_PORT | Database connection port |
RDS_DATABASE | Database name (postgres) |
RDS_DB_USER | Database master username |
CONFIG_SECRET_ARN | Secrets Manager ARN for app config |
ALLOWED_FRONTEND_ORIGINS | Comma-separated allowed origins |
Optional HTTP Proxy Toggle
GitGazer supports an optional HTTP proxy Lambda for outbound calls to IPv4-only upstream services (for example GitHub and Slack).
- Terraform variable:
enable_http_proxy - Default:
true
When enabled, backend calls using proxyFetch relay through the proxy Lambda.
When disabled, the backend falls back to direct fetchWithRetry.
Set it in infra/terraform.tfvars:
enable_http_proxy = false
Then apply Terraform:
cd infra
aws-vault exec <profile> -- terraform apply
With enable_http_proxy = false, your backend environment must provide direct connectivity to target upstreams. In IPv6-only private subnet setups without IPv4 egress, calls to IPv4-only services can fail.
Optional Infrastructure Monitoring Toggle
Infrastructure alarming and Lambda log error filters can be toggled globally.
- Terraform variable:
enable_cloudwatch_alarm_notifications - Default:
true
When disabled, Terraform creates no CloudWatch alarms, no Lambda log metric filters, and no SNS alarm topic.
Set it in infra/terraform.tfvars:
enable_cloudwatch_alarm_notifications = false
Then apply Terraform:
cd infra
aws-vault exec <profile> -- terraform apply
See Infrastructure Monitoring & Alerting for full alarm coverage and notification behavior.
Database Access
Connect to Aurora PostgreSQL via SSM Session Manager port forwarding through the bastion host. No SSH keys or open inbound ports required.
Quick Start
The db:tunnel script automatically discovers the bastion instance and RDS Proxy endpoint, then opens a port-forwarding session:
aws-vault exec <profile> --no-session -- pnpm run db:tunnel
You should see output like:
ℹ Looking up bastion instance gitgazer-bastion-prod in eu-central-1...
✔ Found bastion: i-0abc123def456
ℹ Looking up RDS Proxy endpoint gitgazer-prod...
✔ Found RDS Proxy: gitgazer-prod.proxy-xxxx.eu-central-1.rds.amazonaws.com
ℹ Starting SSM port-forwarding session...
Local: localhost:5432
Remote: gitgazer-prod.proxy-xxxx.eu-central-1.rds.amazonaws.com:5432
✔ Connect with: psql -h localhost -p 5432 -U root
Keep this terminal open — the tunnel stays active until you press Ctrl+C.
Options
| Flag | Default | Description |
|---|---|---|
--local-port PORT | 5432 | Local port to bind (use a different port if 5432 is taken) |
--workspace NAME | prod | Terraform workspace to target (e.g. default, staging) |
--region REGION | eu-central-1 | AWS region override |
Example with a custom local port targeting the default workspace:
aws-vault exec <profile> --no-session -- pnpm run db:tunnel --workspace default --local-port 5433
Prerequisites
- AWS CLI v2 installed and configured
- Session Manager plugin installed
- IAM permissions for
ssm:StartSessionandec2:DescribeInstances
Connect to the Database
With the tunnel running, the database is available at localhost:5432 (or your chosen port). Open Drizzle Studio in a separate terminal:
cd apps/api
npx drizzle-kit studio
Or connect with any PostgreSQL client using localhost:5432 and the credentials from your .env file.
Manual Tunnel (Alternative)
If you prefer to construct the command yourself, Terraform outputs the full SSM command:
cd infra
aws-vault exec <profile> -- terraform output bastion_ssm_port_forward_command
Running Tests
cd apps/api
pnpm run test:unit
Tests use Vitest and mock all AWS services — they never call real APIs.
Troubleshooting
Cannot Connect to API (Local Backend)
- Verify the backend is running:
lsof -i :8080 - Check
.env.localhasVITE_REST_API_ENDPOINT="https://app.gitgazer.localhost:5173/api" - Restart both frontend and backend
CORS Error (Production Backend)
- Verify production
ALLOWED_FRONTEND_ORIGINSincludeshttps://app.gitgazer.localhost:5173 - Clear browser cookies for
gitgazerdomains - Try incognito mode
Authentication Redirect Fails
- Confirm
app.gitgazer.localhostis in your hosts file - Verify Cognito has
https://app.gitgazer.localhost:5173as an allowed callback URL - Ensure
VITE_HOST_URL="https://app.gitgazer.localhost:5173"in.env.local - Clear all cookies and try again
Backend Won't Start
- Verify AWS credentials:
aws-vault exec <profile> -- aws sts get-caller-identity - Check
.envhas the correctAWS_REGION - Ensure your IAM role has permissions for RDS, Secrets Manager, SQS, and KMS
Port Already in Use
# Find and kill the process on the port
lsof -i :5173 # frontend
lsof -i :8080 # backend
kill -9 <PID>