Multi-Region Architecture
Juniro operates in two regions: US and India. This document explains the data residency model and how frontends should handle region selection.
Why Two Regions?
- Data Residency Compliance: Personal data stays in the user's country
- Payment Processing: US uses Stripe, India uses Razorpay
- Performance: Lower latency for users in each region
- Legal Requirements: Each region may have different regulations
Key Principle: Home Region is Immutable
When a user signs up, they choose their home region. This choice is:
- Set once during account creation
- Never changes (immutable)
- Determines which database stores their data
- Controls which API they connect to
┌─────────────────────────────────────────────────────────────────┐
│ User Sign Up Flow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ User → Supabase Auth → Frontend asks "Where are you?" → │
│ POST /auth/sync { home_region: "us" | "in" } │
│ │
│ ✓ User record created in regional database │
│ ✓ home_region is now permanent │
│ ✓ All PII stays in that region forever │
│ │
└─────────────────────────────────────────────────────────────────┘
API Endpoints by Region
| Region | API URL | Database | Payments |
|---|---|---|---|
| US | api.us.juniro.com | US PostgreSQL | Stripe |
| India | api.in.juniro.com | India PostgreSQL | Razorpay |
Local Development
For local dev, a single API instance with REGION=us is sufficient. The region is just a config value.
# .env
REGION=us # or 'in'
PORT=5001
Frontend Implementation
1. Region Selection During Signup
When a new user signs up, show a region selector before calling /auth/sync:
// components/RegionSelector.tsx
const regions = [
{ value: 'us', label: 'United States', flag: '🇺🇸' },
{ value: 'in', label: 'India', flag: '🇮🇳' },
]
function RegionSelector({ onSelect }: { onSelect: (region: 'us' | 'in') => void }) {
return (
<div className="space-y-4">
<h2>Where are you located?</h2>
<p className="text-muted">This determines where your data is stored and cannot be changed later.</p>
{regions.map((region) => (
<button
key={region.value}
onClick={() => onSelect(region.value as 'us' | 'in')}
className="flex items-center gap-3 w-full p-4 border rounded-lg hover:bg-accent"
>
<span className="text-2xl">{region.flag}</span>
<span>{region.label}</span>
</button>
))}
</div>
)
}
2. Syncing User After Auth
After Supabase authentication, sync the user to Juniro:
// lib/api.ts
async function syncUserToJuniro(token: string, homeRegion: 'us' | 'in') {
// Determine API URL based on selected region
const apiUrl = homeRegion === 'us'
? 'https://api.us.juniro.com'
: 'https://api.in.juniro.com'
const response = await fetch(`${apiUrl}/v1/auth/sync`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ home_region: homeRegion }),
})
const data = await response.json()
if (data.success) {
// Store the correct API URL for this user
localStorage.setItem('juniro_api_url', apiUrl)
localStorage.setItem('juniro_home_region', data.data.homeRegion)
}
return data
}
3. Checking Region on App Load
For returning users, verify they're connected to the right region:
// hooks/useRegionCheck.ts
async function checkUserRegion(token: string) {
const storedApiUrl = localStorage.getItem('juniro_api_url') || 'https://api.us.juniro.com'
const response = await fetch(`${storedApiUrl}/v1/me/region`, {
headers: {
'Authorization': `Bearer ${token}`,
},
})
const data = await response.json()
if (data.success && !data.data.isCorrectRegion) {
// User is connected to wrong region - redirect
const correctUrl = data.data.correctApiUrl
localStorage.setItem('juniro_api_url', correctUrl)
// Notify user and refresh
alert(`Redirecting to ${data.data.userHomeRegion.toUpperCase()} servers...`)
window.location.reload()
}
return data
}
4. API Client Configuration
Create an API client that uses the correct regional URL:
// lib/api-client.ts
class JuniroApiClient {
private baseUrl: string
constructor() {
// Default to US, but check localStorage
this.baseUrl = localStorage.getItem('juniro_api_url') || 'https://api.us.juniro.com'
}
setRegion(region: 'us' | 'in') {
this.baseUrl = region === 'us'
? 'https://api.us.juniro.com'
: 'https://api.in.juniro.com'
localStorage.setItem('juniro_api_url', this.baseUrl)
}
async fetch(path: string, options: RequestInit = {}) {
const token = await getAuthToken() // Get from Supabase
return fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
})
}
}
export const api = new JuniroApiClient()
API Response: Region Check
The /v1/me/region endpoint returns:
{
"success": true,
"data": {
"userHomeRegion": "us",
"serverRegion": "us",
"isCorrectRegion": true,
"correctApiUrl": "https://api.us.juniro.com"
}
}
If isCorrectRegion is false, the frontend should redirect the user to the correct API.
Data That Never Crosses Regions
| Data Type | Stays in Region | Notes |
|---|---|---|
| User profiles | ✅ Yes | First name, last name, email, phone |
| Children data | ✅ Yes | Names, birthdates |
| Bookings | ✅ Yes | Linked to regional users |
| Payments | ✅ Yes | Different processors per region |
| Reviews | ✅ Yes | Written by regional users |
Data That Can Be Shared
| Data Type | Shared | Notes |
|---|---|---|
| Activity listings | ✅ Yes | Public catalog, no PII |
| Provider business info | ✅ Yes | Public business details |
| Category/tags | ✅ Yes | Shared taxonomy |
| Anonymized analytics | ✅ Yes | No PII |
Development Workflow
Running Locally
For local development, run a single API instance. Use .env to set the region:
# Start with US region
REGION=us npm run dev
# Or India region
REGION=in npm run dev
Testing Both Regions
If you need to test cross-region behavior locally:
# Terminal 1: US API
PORT=5001 REGION=us npm run dev
# Terminal 2: India API (separate database)
PORT=5002 REGION=in DATABASE_URL=postgresql://...india... npm run dev
Production Architecture
┌─────────────────────────────────────────────────────────────────┐
│ US Region │
├─────────────────────────────────────────────────────────────────┤
│ juniro.com ──→ api.us.juniro.com ──→ PostgreSQL (us-central1) │
│ │ │
│ Supabase US │
│ Stripe │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ India Region │
├───────────────────────────────────── ────────────────────────────┤
│ juniro.com ──→ api.in.juniro.com ──→ PostgreSQL (asia-south1) │
│ │ │
│ Supabase India │
│ Razorpay │
└─────────────────────────────────────────────────────────────────┘
Rules for AI Agents
When working on Juniro code:
- Always check
REGIONconfig - Know which region the code is targeting - Never hardcode API URLs - Use the region-aware API client
- Test with mock region - Use
REGION=usorREGION=inin .env - PII stays in region - Never copy user data between databases
- Region selection is one-time - The signup flow sets it, nothing changes it
Related Documentation
- API Architecture - Overall API design
- API Mandate - API standards and documentation
- URLs Reference - All service URLs including regional APIs