Version: 1.2
Last Updated: April 24, 2026
Author: Manus AI
The Dexcom EGV Tester is a full-stack web application designed to authenticate with the Dexcom CGM (Continuous Glucose Monitor) API via OAuth2 and retrieve Estimated Glucose Values (EGVs). It provides a developer-focused interface for testing both the Dexcom Sandbox and Production API environments, visualizing glucose data on an interactive timeline chart, and exporting results in multiple formats.
The application runs in single-user mode — no login or account creation is required. Anyone who visits the app can connect to Dexcom, fetch data, and export results. A single set of OAuth tokens is stored per environment (Sandbox and Production), meaning the last person to authenticate "owns" the active connection.
The application follows a standard three-tier architecture with a React frontend, a Node.js/Express backend, and a MySQL-compatible database.
┌─────────────────────────────────────────────────────────┐
│ Browser (Client) │
│ React 19 + Tailwind CSS 4 + Recharts + tRPC Client │
└──────────────────────┬──────────────────────────────────┘
│ HTTP (tRPC over /api/trpc)
│ Express routes (/api/dexcom/*)
▼
┌─────────────────────────────────────────────────────────┐
│ Node.js Server (Backend) │
│ Express 4 + tRPC 11 + Axios + Drizzle ORM │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ tRPC Router │ │ Dexcom OAuth │ │ Dexcom API │ │
│ │ (procedures)│ │ (Express) │ │ (proxy calls) │ │
│ └──────┬──────┘ └──────┬───────┘ └────────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Dexcom Service Layer (dexcom.ts) │ │
│ │ Token management, API calls, token refresh │ │
│ └──────────────────────┬───────────────────────────┘ │
└─────────────────────────┼───────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────┐
│ TiDB Cloud (MySQL) │ │ Dexcom API Servers │
│ Token storage │ │ sandbox-api.dexcom.com │
│ User records │ │ api.dexcom.com │
└──────────────────────┘ └──────────────────────────────┘
All frontend-to-backend communication uses tRPC (type-safe RPC over HTTP), except for the Dexcom OAuth redirect flow which uses standard Express GET routes. The backend proxies all Dexcom API calls, ensuring the Client Secret never reaches the browser. Tokens are stored in the database and automatically refreshed when they expire (with a 60-second buffer).
The application depends on three external services. The table below summarizes each service, its role, and the credentials required.
| Service | Purpose | Credentials Required | Free Tier |
|---|---|---|---|
| Dexcom API | OAuth2 authentication and EGV data retrieval | DEXCOM_CLIENT_ID, DEXCOM_CLIENT_SECRET |
Yes (Sandbox is free; Production requires approval) |
| TiDB Cloud | MySQL-compatible database for storing OAuth tokens | DATABASE_URL (connection string) |
Yes (Serverless Starter: 5 GiB storage, 50M Request Units/month) |
| Render | Web hosting and deployment | GitHub connection | Yes (Free tier: 750 hours/month, auto-sleep after 15 min inactivity) |
The Dexcom API 1 provides programmatic access to CGM data. The application uses two environments:
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://sandbox-api.dexcom.com |
Testing with simulated data from predefined test users |
| Production | https://api.dexcom.com |
Real patient data (requires Dexcom approval) |
The following Dexcom API endpoints are used:
| Endpoint | Method | Description |
|---|---|---|
/v3/oauth2/login |
GET | Initiates the OAuth2 authorization flow (user redirect) |
/v3/oauth2/token |
POST | Exchanges authorization codes for tokens, and refreshes expired tokens |
/v3/users/self/egvs |
GET | Retrieves Estimated Glucose Values for a date range (max 30 days) |
/v3/users/self/dataRange |
GET | Returns the available date range for the connected user's data |
The Dexcom API enforces a maximum 30-day query window per request. Dates must be in ISO 8601 format. The application validates this constraint on both the frontend and backend before making API calls.
TiDB Cloud 2 provides a MySQL-compatible serverless database. The application uses it to persist Dexcom OAuth tokens (access token, refresh token, expiration time) so that users do not need to re-authenticate on every visit. The connection requires TLS/SSL, which is enforced by appending ?ssl={"rejectUnauthorized":true} to the connection string.
Render 3 hosts the application as a Node.js web service. The render.yaml file in the repository root provides a declarative deployment configuration (Infrastructure as Code). On the free tier, the service will spin down after 15 minutes of inactivity and cold-start on the next request (which may take 30–60 seconds).
The database contains two tables managed by Drizzle ORM. Migrations are applied via pnpm db:push (which runs drizzle-kit generate && drizzle-kit migrate).
This table exists as part of the template scaffolding. In single-user mode, it contains a single row with id = 1 that serves as the foreign key anchor for token storage.
| Column | Type | Description |
|---|---|---|
id |
INT (auto-increment, PK) | Surrogate primary key |
openId |
VARCHAR(64), UNIQUE | OAuth identifier (set to single-user in this mode) |
name |
TEXT | Display name |
email |
VARCHAR(320) | Email address |
loginMethod |
VARCHAR(64) | Authentication method used |
role |
ENUM(user, admin) |
User role (defaults to user) |
createdAt |
TIMESTAMP | Record creation time |
updatedAt |
TIMESTAMP | Last update time (auto-updated) |
lastSignedIn |
TIMESTAMP | Last sign-in time |
Stores Dexcom OAuth tokens per environment. In single-user mode, userId is always 1.
| Column | Type | Description |
|---|---|---|
id |
INT (auto-increment, PK) | Surrogate primary key |
userId |
INT | References users.id (always 1 in single-user mode) |
accessToken |
TEXT | Dexcom OAuth access token |
refreshToken |
TEXT | Dexcom OAuth refresh token (used to obtain new access tokens) |
expiresAt |
TIMESTAMP | When the current access token expires |
sandboxUser |
VARCHAR(64) | Which sandbox test user was selected (sandbox only) |
environment |
ENUM(sandbox, production) |
Which Dexcom environment this token belongs to |
createdAt |
TIMESTAMP | Record creation time |
updatedAt |
TIMESTAMP | Last update time (auto-updated) |
The combination of userId + environment is unique in practice — each environment stores exactly one set of tokens.
The Dexcom OAuth2 flow follows the standard Authorization Code Grant pattern. The application implements this across two Express routes and the Dexcom service layer.
Step 1 — Authorization Request: The frontend calls GET /api/dexcom/authorize?origin=<app_url>&env=<sandbox|production>. The backend constructs the Dexcom authorization URL with the Client ID, redirect URI, and a Base64-encoded state parameter (containing the origin URL and environment). The frontend then redirects the user's browser to this URL.
Step 2 — User Authorization: The user signs in on Dexcom's website. In sandbox mode, they select a test user from a dropdown. In production mode, they enter their real Dexcom account credentials.
Step 3 — Callback: Dexcom redirects back to GET /api/dexcom/callback?code=<auth_code>&state=<state>. The backend decodes the state to recover the origin and environment, then exchanges the authorization code for an access token and refresh token via a POST to Dexcom's token endpoint.
Step 4 — Token Storage: The tokens are saved to the dexcom_tokens table in the database, keyed by userId (always 1) and environment.
Step 5 — Redirect: The user is redirected back to the app with ?dexcom_connected=true&env=<env> in the URL. The frontend detects this, shows a success toast, and switches to the correct environment tab.
Token Refresh: When a tRPC procedure needs an access token, it calls getValidAccessToken(). If the stored token expires within 60 seconds, the function automatically refreshes it using the refresh token and updates the database. This is transparent to the user.
Error Handling: If the token exchange fails (e.g., "max user count exceeded"), the backend extracts a human-readable error from Dexcom's response (which can come in multiple formats) and redirects with ?dexcom_error=<message>&env=<env>. The frontend displays this as a toast notification.
The table below maps each significant file to its responsibility in the application.
| File Path | Responsibility |
|---|---|
server/dexcom.ts |
Core Dexcom service layer — OAuth URL generation, token exchange, token refresh, token storage/retrieval, EGV and data range API calls |
server/dexcomRoutes.ts |
Express routes for OAuth flow (/api/dexcom/authorize, /api/dexcom/callback) |
server/routers.ts |
tRPC router definitions — dexcom.status, dexcom.disconnect, dexcom.dataRange, dexcom.egvs procedures |
server/db.ts |
Database connection helper (lazy initialization) and user upsert/query functions |
server/_core/index.ts |
Express server entry point — registers tRPC middleware, Dexcom routes, and Vite dev middleware |
server/_core/env.ts |
Environment variable parsing and validation |
drizzle/schema.ts |
Database table definitions (users, dexcom_tokens) using Drizzle ORM |
shared/const.ts |
Shared constants — Dexcom base URLs, environment types, timezone mode type |
client/src/pages/Home.tsx |
Main UI — tabbed interface (Connect, EGV Data, Health Correlations, API Info), environment toggle, date inputs, data table |
client/src/components/EgvChart.tsx |
Recharts-based glucose timeline chart with target range highlighting and trend tooltips |
client/src/components/JsonViewer.tsx |
Syntax-highlighted JSON viewer for raw API responses |
client/src/lib/timezone.ts |
Timezone conversion utilities — UTC/local formatting, input-to-API date conversion |
client/src/lib/export.ts |
Export utilities — CSV, JSON, and PNG (SVG-to-Canvas) chart export |
render.yaml |
Render deployment configuration (Infrastructure as Code) |
DEPLOY.md |
Step-by-step deployment guide for Render + TiDB Cloud |
A toggle in the header switches between Sandbox and Production environments. The application defaults to Production on page load. Each environment maintains independent OAuth tokens and connection state. Production mode displays a warning banner reminding users that real patient data is being accessed.
A UTC/Local toggle in the header controls how all dates and times are displayed throughout the application. In UTC mode, all timestamps are shown in ISO 8601 UTC format. In Local mode, timestamps are converted to the user's browser timezone (e.g., EST). The date input fields, chart X-axis labels, chart tooltips, and data table all respect this setting. Regardless of display mode, all dates are converted to UTC before being sent to the Dexcom API.
The glucose chart uses Recharts to render an interactive timeline with the following visual elements:
| Element | Description |
|---|---|
| Green shaded area (80–180 mg/dL) | Target glucose range |
| Amber dashed lines (80, 180) | Low and high thresholds |
| Red dashed line (54) | Urgent low threshold |
| Teal line | Glucose readings over time |
| Hover tooltip | Shows exact value, time, trend arrow, and rate of change |
| Average glucose badge | Displayed next to the chart title, color-coded: green (80–180), red (<80), amber (>180) |
The chart X-axis uses smart axis labels that adapt to the date range being displayed. When the data spans a single day or less, only times are shown (e.g., "02:30 PM"). When the data spans multiple days, the axis switches to a date+time format (e.g., "01/15 14:30") with slightly angled labels to prevent overlap.
Three export formats are available once EGV data is loaded:
| Format | Contents | Filename Pattern |
|---|---|---|
| CSV | All EGV record fields plus a formatted display time column | dexcom-egvs_<env>_<timestamp>.csv |
| JSON | Raw Dexcom API response with pretty-print indentation | dexcom-egvs_<env>_<timestamp>.json |
| PNG | Glucose chart with metadata header (date range, average glucose, record count) rendered at 2x resolution via SVG-to-Canvas | dexcom-chart_<env>_<timestamp>.png |
The PNG export includes a header above the chart containing: the chart title with timezone label, the average glucose value (color-coded by range), formatted start and end dates, the date range duration (hours or days), the total record count, and the environment label (Production or Sandbox).
The application requires the following environment variables in production. These are configured in Render's Environment settings.
| Variable | Required | Description | Where to Obtain |
|---|---|---|---|
DATABASE_URL |
Yes | MySQL connection string with TLS | TiDB Cloud dashboard > Connect > Connection String |
DEXCOM_CLIENT_ID |
Yes | Dexcom developer app Client ID | Dexcom Developer Portal > My Apps |
DEXCOM_CLIENT_SECRET |
Yes | Dexcom developer app Client Secret | Dexcom Developer Portal > My Apps |
JWT_SECRET |
Yes | Random string for session cookie signing | Auto-generated by render.yaml, or set any random string |
NODE_ENV |
Yes | Must be production for deployed builds |
Set to production in Render |
PORT |
No | Server port (Render sets this automatically) | Managed by Render |
The Dexcom OAuth flow requires a Redirect URI registered in your Dexcom developer app settings. This URI must exactly match the callback URL the application uses.
| Hosting Environment | Redirect URI |
|---|---|
| Render (production) | https://dexcom-egv-tester.onrender.com/api/dexcom/callback |
| Manus (development) | https://<manus-preview-url>/api/dexcom/callback |
| Local development | http://localhost:3000/api/dexcom/callback |
You can register multiple redirect URIs in the Dexcom developer portal simultaneously. The application dynamically constructs the correct callback URL based on the origin parameter passed during the authorization request.
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| Frontend framework | React | 19.2 | UI rendering and component architecture |
| Styling | Tailwind CSS | 4.1 | Utility-first CSS framework |
| UI components | shadcn/ui (Radix primitives) | Various | Accessible, composable UI components |
| Charting | Recharts | 2.15 | Interactive glucose timeline chart |
| Client-server RPC | tRPC | 11.6 | Type-safe API calls between frontend and backend |
| Data fetching | TanStack React Query | 5.90 | Server state management, caching, and refetching |
| Routing | Wouter | 3.3 | Lightweight client-side routing |
| Backend framework | Express | 4.21 | HTTP server and middleware |
| HTTP client | Axios | 1.12 | Server-side HTTP requests to Dexcom API |
| ORM | Drizzle ORM | 0.44 | Type-safe database queries and schema management |
| Database driver | mysql2 | 3.15 | MySQL/TiDB connection driver |
| Schema validation | Zod | 4.1 | Input validation for tRPC procedures |
| Serialization | SuperJSON | 1.13 | Preserves Date objects across tRPC boundary |
| Build tool | Vite | 7.1 | Frontend bundling and dev server |
| Server bundler | esbuild | 0.25 | Server-side code bundling for production |
| TypeScript | TypeScript | 5.9 | Type safety across the full stack |
| Testing | Vitest | 2.1 | Unit testing framework |
| Package manager | pnpm | 10.4 | Fast, disk-efficient package management |
If your Dexcom Client ID or Secret changes, update the DEXCOM_CLIENT_ID and DEXCOM_CLIENT_SECRET environment variables in Render's dashboard (Settings > Environment). The service will automatically redeploy.
If you rotate the TiDB Cloud password, update the DATABASE_URL environment variable in Render with the new connection string. Ensure the ?ssl={"rejectUnauthorized":true} suffix is preserved.
To add support for additional Dexcom endpoints (e.g., Calibrations, Devices, Events, Alerts):
- Add a new fetch function in
server/dexcom.tsfollowing the pattern offetchEgvData(). - Add a new tRPC procedure in
server/routers.tsunder thedexcomrouter. - Add a new UI tab or section in
client/src/pages/Home.tsxthat calls the new procedure. - Write a vitest test in
server/dexcom.routers.test.tsfor the new procedure.
To support multiple users with independent Dexcom connections, you would need to:
- Re-enable authentication (add an auth provider like Auth0, Clerk, or email/password).
- Replace the
SINGLE_USER_ID = 1constant inserver/routers.tsandserver/dexcomRoutes.tswith the authenticated user's ID from the session. - The database schema already supports per-user tokens via the
userIdcolumn, so no schema changes are needed.
Render provides built-in logging. You can view server logs in the Render dashboard under your service's Logs tab. Key log prefixes to watch for:
| Log Prefix | Meaning |
|---|---|
[Dexcom] Callback error: |
OAuth token exchange failed — check the error details |
[Dexcom] Failed to refresh token |
Token refresh failed — user may need to re-authenticate |
[Database] Failed to connect: |
Database connection issue — verify DATABASE_URL |
Render's free tier spins down the service after 15 minutes of inactivity. The first request after a cold start may take 30–60 seconds. If this is unacceptable, consider upgrading to Render's paid tier ($7/month for the Starter plan) which keeps the service running continuously.
Run the full test suite with:
pnpm testTests are located in server/dexcom.routers.test.ts, server/dexcom.credentials.test.ts, server/auth.logout.test.ts, client/src/lib/export.test.ts, client/src/lib/splitDateRange.test.ts, and server/appleHealth.test.ts. They validate date range logic, date range chunking, tRPC procedure behavior, credential configuration, export utilities, Apple Health parsing, aggregation, and correlation calculations.
To run the application locally:
# Install dependencies
pnpm install
# Set environment variables (create a .env file or export them)
export DATABASE_URL="mysql://..."
export DEXCOM_CLIENT_ID="your_client_id"
export DEXCOM_CLIENT_SECRET="your_client_secret"
export JWT_SECRET="any_random_string"
# Start the dev server (hot-reloading enabled)
pnpm devThe dev server runs on http://localhost:3000. Remember to add http://localhost:3000/api/dexcom/callback as a redirect URI in your Dexcom developer app settings for local testing.