Skip to main content

Server

The ChartChat server is a Node.js / Express backend that provides the FHIR API, authentication, EHR integration, and all business logic for the platform. It is built on top of the open-source Medplum server with ChartChat-specific extensions.

Directory: packages/server/
Port: 8103 (default)
Entry point: src/index.ts


Key Responsibilities

AreaDescription
FHIR R4Full FHIR resource CRUD, search, subscriptions, batch operations
AuthenticationOAuth2 / PKCE — email/password, Google, Microsoft, SMART on FHIR
EHR importOAuth connect + background sync from Epic, Healow, MEDITECH
Background jobsBullMQ workers for bots, async batch, cron, subscriptions
AIPatient summary, chat context, embed sync
SecurityCSRF, rate limiting, PHI breach detection, audit logging
AdminUser management, bot execution, project configuration

Project Structure

packages/server/src/
├── app.ts ← Express app setup, all route registration
├── index.ts ← Server entry point
├── config/ ← Config loader and type definitions
├── auth/ ← Auth handlers (login, register, MFA, external)
├── oauth/ ← OAuth2 token endpoints and middleware
├── smart/ ← SMART on FHIR launch and patient summary
├── fhir/ ← FHIR R4 routes, resource handlers, operations
├── ehr-import/ ← EHR OAuth flow, sync service, provider configs
├── admin/ ← Admin API routes
├── audit/ ← Audit logging and breach detection
├── security/ ← Security utilities
├── support/ ← Support ticket API
├── workers/ ← BullMQ background job workers
├── migrations/ ← Database schema migrations
├── database.ts ← PostgreSQL connection pool
├── redis.ts ← Redis client
├── ratelimit.ts ← Per-IP rate limiting
├── csrf.ts ← CSRF token validation
└── cors.ts ← CORS allowed origins

Runtime Configuration

The server reads its configuration from two sources at startup:

medplum.config.json

Located in the server's CWD (the release directory in production, packages/server/ locally). Contains non-secret configuration: port, base URL, database connection, Redis connection, feature flags, etc.

{
"port": 8103,
"baseUrl": "http://localhost:8103/",
"appBaseUrl": "http://localhost:3000/",
"binaryStorage": "file:./binary/",
"storageBaseUrl": "http://localhost:8103/storage/",
"awsRegion": "us-east-2",
"supportEmail": "\"ChartChat\" <support@chartchathealth.com>",
"maxJsonSize": "1mb",
"maxBatchSize": "50mb",
"vmContextBotsEnabled": true,
"defaultBotRuntimeVersion": "vmcontext",
"allowedOrigins": "*",
"introspectionEnabled": true,
"saveAuditEvents": true,
"logAuditEvents": true,
"logRequests": true,
"database": {
"host": "localhost",
"port": 5432,
"dbname": "medplum",
"username": "medplum",
"password": "medplum"
},
"redis": {
"host": "localhost",
"port": 6379,
"password": "medplum"
},
"smtp": {
"host": "smtp.gmail.com",
"port": 465,
"username": "help@chartchathealth.com",
"password": "your-smtp-password"
},
"bullmq": {
"removeOnFail": { "count": 100 },
"removeOnComplete": { "count": 100 }
},
"bullBoardPassword": "your-bullboard-password",
"shutdownTimeoutMilliseconds": 30000
}

.env

Contains all secrets and runtime overrides. Create this file at packages/server/.env for local development. Never commit it to version control.

# ── Core ──────────────────────────────────────────────
MEDPLUM_BASE_URL=http://localhost:8103/
MEDPLUM_CLOUD_BASE_URL=https://api.medplum.com/
MEDPLUM_CLIENT_ID=your-medplum-client-id
MEDPLUM_FHIR_URL=http://localhost:8103/fhir/R4/
MEDPLUM_PROJECT_ID=your-medplum-project-id
MEDPLUM_CALLBACK_URL=http://localhost:3000/smart/medplum/callback
MEDPLUM_REGISTER_ENABLED=true

# ── Frontend ──────────────────────────────────────────
FRONTEND_URL=http://localhost:3000

# ── Epic EHR ─────────────────────────────────────────
EPIC_CLIENT_ID=your-epic-client-id
EPIC_REDIRECT_URI=http://localhost:8103/fhir/ehr-import/callback

# ── Healow EHR ────────────────────────────────────────
HEALOW_CLIENT_ID=your-healow-client-id
HEALOW_REDIRECT_URI=http://localhost:8103/fhir/ehr-import/callback

# ── MEDITECH EHR ─────────────────────────────────────
MEDITECH_CLIENT_ID=your-meditech-client-id
MEDITECH_CLIENT_SECRET=your-meditech-client-secret
MEDITECH_REDIRECT_URI=http://localhost:8103/fhir/ehr-import/callback

# ── Security ─────────────────────────────────────────
EHR_TOKEN_ENCRYPTION_KEY=your-32-byte-hex-key

# ── AI ───────────────────────────────────────────────
VITE_GROQ_API_KEY=your-groq-api-key
EMBED_API_URL=http://localhost:8104/

# ── Auth ─────────────────────────────────────────────
GOOGLE_CLIENT_ID=your-google-client-id
RECAPTCHA_SITE_KEY=your-recaptcha-site-key

# ── Defaults ─────────────────────────────────────────
CHARTCHAT_DEFAULT_PASSWORD=your-default-admin-password

Environment Variable Reference

VariableRequiredDescription
MEDPLUM_BASE_URLYesChartChat server base URL
MEDPLUM_CLOUD_BASE_URLNoMedplum cloud API URL
MEDPLUM_CLIENT_IDYesOAuth ClientApplication ID used by the server
MEDPLUM_FHIR_URLYesFHIR R4 base URL
MEDPLUM_PROJECT_IDYesMedplum project ID
MEDPLUM_CALLBACK_URLNoOAuth callback URL for SMART auth
MEDPLUM_REGISTER_ENABLEDNoAllow new account registration. Default: true
FRONTEND_URLYesMember Portal URL — used for EHR OAuth redirect after callback
EPIC_CLIENT_IDNoEpic MyChart OAuth client ID
EPIC_REDIRECT_URINoEpic OAuth redirect URI
HEALOW_CLIENT_IDNoHealow (eClinicalWorks) OAuth client ID
HEALOW_REDIRECT_URINoHealow OAuth redirect URI
MEDITECH_CLIENT_IDNoMEDITECH OAuth client ID
MEDITECH_CLIENT_SECRETNoMEDITECH OAuth client secret
MEDITECH_REDIRECT_URINoMEDITECH OAuth redirect URI
EHR_TOKEN_ENCRYPTION_KEYYesAES encryption key for stored EHR tokens. Must be a 32-byte hex string.
VITE_GROQ_API_KEYNoGroq API key for AI chat and patient summary
EMBED_API_URLNoBase URL of the AI embedding service. Called as POST {EMBED_API_URL}/api/embed/{patientId}/sync after every EHR sync to re-index patient records for the AI chat. Defaults to https://dev-chatapi.chartchathealth.com.
GOOGLE_CLIENT_IDNoGoogle OAuth client ID
RECAPTCHA_SITE_KEYNoGoogle reCAPTCHA site key
CHARTCHAT_DEFAULT_PASSWORDNoDefault password for seeded admin accounts
tip

EHR_TOKEN_ENCRYPTION_KEY must be set to a cryptographically random 32-byte key in production. Generate one with:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

How to Run Locally

See Local Setup for the full guide. Summary:

# 1. Start PostgreSQL + Redis
docker-compose up # from repo root

# 2. Build shared packages
npm run build:fast

# 3. Start the server
cd packages/server
npm install
npm run dev

The server starts at http://localhost:8103.