Skip to main content

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?

  1. Data Residency Compliance: Personal data stays in the user's country
  2. Payment Processing: US uses Stripe, India uses Razorpay
  3. Performance: Lower latency for users in each region
  4. 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

RegionAPI URLDatabasePayments
USapi.us.juniro.comUS PostgreSQLStripe
Indiaapi.in.juniro.comIndia PostgreSQLRazorpay

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 TypeStays in RegionNotes
User profiles✅ YesFirst name, last name, email, phone
Children data✅ YesNames, birthdates
Bookings✅ YesLinked to regional users
Payments✅ YesDifferent processors per region
Reviews✅ YesWritten by regional users

Data That Can Be Shared

Data TypeSharedNotes
Activity listings✅ YesPublic catalog, no PII
Provider business info✅ YesPublic business details
Category/tags✅ YesShared taxonomy
Anonymized analytics✅ YesNo 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:

  1. Always check REGION config - Know which region the code is targeting
  2. Never hardcode API URLs - Use the region-aware API client
  3. Test with mock region - Use REGION=us or REGION=in in .env
  4. PII stays in region - Never copy user data between databases
  5. Region selection is one-time - The signup flow sets it, nothing changes it