A generic, RESTful API for storing and querying health data with PostgreSQL backend. Designed to be consumed by mobile apps, MCP servers, and other health data clients.
This service acts as the single source of truth for all your health data. It provides a clean, generic API that can:
- Accept health samples from multiple sources (iOS app, wearables, manual entry)
- Store data in a structured PostgreSQL database
- Provide query APIs for retrieving and analyzing data
- Serve multiple consumers (MCP servers, dashboards, analytics tools)
βββββββββββββββββββ
β Data Sources β
β - iOS App β
β - Wearables β
β - Manual Entry β
ββββββββββ¬βββββββββ
β POST /api/samples
βΌ
ββββββββββββββββββββββββ
β health-data-storage β β YOU ARE HERE
β - REST API β
β - PostgreSQL β
ββββββββββ¬ββββββββββββββ
β GET /api/samples/*
β
ββββββ΄βββββ¬βββββββββββββ
βΌ βΌ βΌ
ββββββββββ ββββββββ ββββββββββββ
βMCP β βWeb β βAnalytics β
βServers β βDashboardβ β Tools β
ββββββββββ ββββββββ ββββββββββββ
The system uses 7 PostgreSQL tables to store all health, nutrition, and user data:
id SERIAL PRIMARY KEY
google_id VARCHAR(255) UNIQUE NOT NULL
email VARCHAR(255) UNIQUE NOT NULL
name VARCHAR(255)
picture_url TEXT
api_key VARCHAR(64) UNIQUE NOT NULL
created_at TIMESTAMPTZ DEFAULT NOW()
last_login_at TIMESTAMPTZ
deleted_at TIMESTAMPTZ -- When account deleted
deletion_scheduled_at TIMESTAMPTZ -- 30-day grace periodGoogle OAuth authentication with unique API keys per user.
client_id VARCHAR(64) PRIMARY KEY
client_secret VARCHAR(64) NOT NULL
name VARCHAR(255) NOT NULL
redirect_uris TEXT[] NOT NULL
allowed_scopes TEXT[] NOT NULL
created_at TIMESTAMPTZ DEFAULT NOW()Registered OAuth clients (MCP servers) that can access user data.
token VARCHAR(255) PRIMARY KEY
user_id INTEGER REFERENCES users(id)
client_id VARCHAR(64) REFERENCES oauth_clients(client_id)
scopes TEXT[] NOT NULL
expires_at TIMESTAMPTZ NOT NULL
refresh_token VARCHAR(255) UNIQUE
created_at TIMESTAMPTZ DEFAULT NOW()Active OAuth access tokens with refresh capability.
id SERIAL PRIMARY KEY
user_id VARCHAR(255) NOT NULL
type VARCHAR(100) NOT NULL -- e.g., "BloodGlucose", "HeartRate"
value NUMERIC NOT NULL
unit VARCHAR(50) NOT NULL -- e.g., "mg/dL", "bpm"
start_date TIMESTAMPTZ NOT NULL
end_date TIMESTAMPTZ NOT NULL
source VARCHAR(255) -- e.g., "Lingo", "Apple Watch"
local_timezone VARCHAR(50) -- e.g., "America/Los_Angeles"
metadata JSONB -- Additional data as JSON
created_at TIMESTAMPTZ DEFAULT NOW()
UNIQUE(user_id, type, start_date, source)Supported Data Types:
BloodGlucose(mg/dL)HeartRate(bpm)Steps(count)BodyMass(kg)ActiveEnergyBurned(kcal)- Any other health metric
id SERIAL PRIMARY KEY
user_id VARCHAR(255) NOT NULL
timestamp TIMESTAMPTZ NOT NULL
local_date DATE -- YYYY-MM-DD in user's timezone
local_time TIME -- HH:MM:SS in user's timezone
local_timezone VARCHAR(50) -- IANA timezone identifier
photo_url TEXT -- URL to photo in GCS bucket
net_carbs DECIMAL NOT NULL -- Net carbs (total - fiber)
total_carbs DECIMAL DEFAULT 0 -- Total carbohydrates
fiber DECIMAL DEFAULT 0 -- Dietary fiber
protein DECIMAL NOT NULL
fat DECIMAL NOT NULL
calories DECIMAL NOT NULL
sugar DECIMAL DEFAULT 0 -- Sugar content
sodium DECIMAL DEFAULT 0 -- Sodium in mg
foods JSONB NOT NULL -- Array of food items
confidence DECIMAL NOT NULL -- AI confidence (0.0 - 1.0)
meal_type VARCHAR(50) -- 'breakfast', 'lunch', 'dinner', 'snack'
manual_override BOOLEAN DEFAULT false
notes TEXT
created_at TIMESTAMPTZ DEFAULT NOW()Stores all meal data with AI-analyzed nutrition info and photos.
id SERIAL PRIMARY KEY
user_id INTEGER NOT NULL REFERENCES users(id)
timestamp TIMESTAMPTZ NOT NULL
local_date DATE
local_time TIME
local_timezone VARCHAR(50)
amount_ml DECIMAL NOT NULL CHECK (amount_ml > 0)
notes TEXT
created_at TIMESTAMPTZ DEFAULT NOW()Tracks daily water intake in milliliters.
user_id INTEGER PRIMARY KEY REFERENCES users(id)
-- Body metrics
height_cm DECIMAL
weight_kg DECIMAL
age INTEGER
gender VARCHAR(20) -- 'male', 'female', 'other'
activity_level VARCHAR(20) -- 'sedentary', 'light', 'moderate', 'very_active', 'extra_active'
-- Dietary goals
dietary_goal VARCHAR(50) -- 'weight_loss', 'maintenance', 'muscle_gain', 'keto'
target_calories DECIMAL
target_net_carbs DECIMAL
target_protein DECIMAL
target_fat DECIMAL
target_fiber DECIMAL
-- Display preferences
show_calories BOOLEAN DEFAULT true
show_carbs BOOLEAN DEFAULT true
show_net_carbs BOOLEAN DEFAULT true
show_fiber BOOLEAN DEFAULT false
show_fat BOOLEAN DEFAULT true
show_protein BOOLEAN DEFAULT true
-- Water tracking
water_tracking_enabled BOOLEAN DEFAULT false
target_water_ml DECIMAL DEFAULT 2000
created_at TIMESTAMPTZ DEFAULT NOW()
updated_at TIMESTAMPTZ DEFAULT NOW()User body metrics, dietary goals, and app display preferences.
idx_health_samples_user_type_date ON health_samples(user_id, type, start_date DESC)
idx_food_logs_user_local_date ON food_logs(user_id, local_date DESC)
idx_water_logs_user_date ON water_logs(user_id, local_date DESC)
idx_oauth_tokens_user ON oauth_tokens(user_id)GET /health
Response:
{
"status": "ok",
"service": "health-data-storage",
"timestamp": "2025-10-22T12:00:00Z"
}POST /api/samples
Headers: X-API-Secret: your-secret
Body:
{
"userId": "[email protected]",
"samples": [
{
"type": "BloodGlucose",
"value": 95,
"unit": "mg/dL",
"startDate": "2025-10-22T10:30:00Z",
"endDate": "2025-10-22T10:30:00Z",
"source": "Lingo",
"metadata": {}
}
]
}
Response:
{
"success": true,
"inserted": 1,
"total": 1
}GET /api/[email protected]&type=BloodGlucose&startDate=2025-10-01T00:00:00Z&endDate=2025-10-22T23:59:59Z&limit=100
Headers: X-API-Secret: your-secret
Response:
{
"count": 10,
"samples": [...]
}GET /api/samples/[email protected]&type=BloodGlucose
Headers: X-API-Secret: your-secret
Response:
{
"id": 123,
"user_id": "[email protected]",
"type": "BloodGlucose",
"value": 95,
"unit": "mg/dL",
"start_date": "2025-10-22T10:30:00Z",
"end_date": "2025-10-22T10:30:00Z",
"source": "Lingo",
"metadata": {},
"created_at": "2025-10-22T10:31:00Z"
}GET /api/samples/[email protected]&type=BloodGlucose&startDate=2025-10-01T00:00:00Z&endDate=2025-10-22T23:59:59Z
Headers: X-API-Secret: your-secret
Response:
{
"count": 100,
"average": 98.5,
"min": 75,
"max": 125,
"unit": "mg/dL"
}- PostgreSQL database (Cloud SQL, Supabase, etc.)
- Node.js 20+
- Google Cloud Platform account (for deployment)
DATABASE_URL=postgresql://user:password@host:5432/dbname
API_SECRET=your-secure-random-secret
PORT=8080
NODE_ENV=production# Install dependencies
npm install
# Build TypeScript
npm run build
# Start server
npm start# Set environment variables
export GOOGLE_CLOUD_PROJECT=your-project-id
export DATABASE_URL=postgresql://...
export API_SECRET=$(openssl rand -base64 32)
# Deploy
chmod +x deploy.sh
./deploy.shAfter deployment, save the service URL and API_SECRET for use in:
- iOS app configuration
- MCP server configuration
- Other clients
API uses a shared secret (X-API-Secret header) for authentication. This is simple but effective for:
- Mobile apps (secret stored securely on device)
- MCP servers (secret in environment variables)
- Trusted clients
For multi-tenant or public-facing deployments, consider implementing OAuth2 or JWT authentication.
Easy to add:
- New data types: Just start sending them! Schema is generic.
- Multi-user support: User management and proper authentication.
- Webhooks: Notify consumers when new data arrives.
- Data export: CSV, JSON bulk exports.
- Aggregations: Daily/weekly/monthly rollups.
- Data retention: Automatic cleanup of old data.
- health-tracking-app: iOS app that POSTs data to this API
- mcp-glucose: MCP server that queries glucose data from this API
- mcp-activity: (future) MCP server for activity data
- mcp-nutrition: (future) MCP server for food/nutrition data
MIT