Database & Redis
ChartChat uses PostgreSQL as its primary data store and Redis for caching, background job queues, and real-time features.
PostgreSQL​
File: packages/server/src/database.ts
The server connects to PostgreSQL using the pg connection pool. The connection is configured via medplum.config.json or environment variables.
Connection pools​
| Pool | Purpose |
|---|---|
Primary (pool) | All writes and most reads |
Read-only (readonlyPool) | Optional — used when readonlyDatabase is configured (e.g., an AWS RDS read replica) |
The getDatabasePool(mode) function returns the appropriate pool:
DatabaseMode.WRITER→ always the primary poolDatabaseMode.READER→ read-only pool if configured, otherwise primary pool
Schema & Migrations​
Directory: packages/server/src/migrations/schema/
The database schema is managed through a sequential migration system. There are currently 107+ migration files (v1.ts through v107.ts).
Migrations run automatically on server startup when runMigrations !== false in the config. The server uses an advisory lock (pg_advisory_lock(1)) to prevent concurrent migrations in multi-instance deployments.
Migration types​
| Type | Description |
|---|---|
| Pre-deploy | Schema changes that must run before the new code is deployed (column adds, index creates) |
| Post-deploy | Data backfills and non-blocking changes that run after deployment |
The maybeAutoRunPendingPostDeployMigration() function runs any pending post-deploy migrations automatically after the server starts.
Running migrations manually​
From the Admin Portal Super Admin page:
- Reconcile Schema Drift — detects and applies any missing schema changes
- Reindex — rebuilds search indexes for a specific resource type
Redis​
File: packages/server/src/redis.ts
Redis is used for multiple purposes across the server.
What's stored in Redis​
| Key pattern | Purpose | TTL |
|---|---|---|
ehr:token:{connectionId} | Raw EHR OAuth token (access + refresh) | Token expiry (~1 hour) |
ehr:sync:status:{patientId} | Current EHR sync state (syncing / idle) | 5 minutes |
ehr:sync:result:{patientId} | Last sync outcome (synced/expired/error providers) | 5 minutes |
oauth:state:{uuid} | PKCE OAuth state for EHR connect flow | Short TTL (single-use) |
| BullMQ job queues | Background worker jobs (bot execution, async FHIR batch, etc.) | Managed by BullMQ |
| WebSocket subscriptions | FHIR subscription notifications | Event-driven |
Connection management​
The server maintains a single global Redis instance (getRedis()). A separate subscriber-mode instance (getRedisSubscriber()) is used for pub/sub — this is required because Redis subscriber connections cannot send regular commands.
On failover (e.g., AWS ElastiCache primary/replica switch), the client automatically reconnects when it receives a READONLY error.
Local setup​
For local development, Redis is started via Docker Compose:
docker-compose up # from the repo root
This starts Redis on the default port 6379. See Local Setup for the full docker-compose configuration.
Background Workers​
File: packages/server/src/workers/
ChartChat uses BullMQ (backed by Redis) for background job processing.
| Worker | Queue | Purpose |
|---|---|---|
| Bot | BotQueue | Executes FHIR subscription bots (VM context or AWS Lambda) |
| Batch | BatchQueue | Processes POST /fhir/R4 async FHIR Bundle jobs |
| Subscription | SubscriptionQueue | Delivers FHIR R4 subscription notifications (webhook + bot dispatch) |
| Download | DownloadQueue | Fetches external URLs in Attachment fields and stores them as Binary resources |
| Cron | CronQueue | Executes bots on a schedule via FHIR Timing resources |
| Reindex | ReindexQueue | Rebuilds search index columns for a given resource type |
| Set Accounts | SetAccountsQueue | Updates patient compartment references after account changes |
| Post-Deploy Migration | PostDeployMigrationQueue | Runs data backfill migrations as background AsyncJob resources |
| Data Retention | DataRetentionQueue | Daily job at 02:00 — purges revoked logins (90-day cutoff) per HIPAA policy |
For full details on data retention and HIPAA compliance, see Audit, Security & Data Retention.
The BullMQ dashboard is available at /api/admin/queues (requires admin access). Password is set via bullBoardPassword in medplum.config.json.
Seeding​
File: packages/server/src/seed.ts
On first startup, the server seeds the database with:
- A default super admin account (configurable via
defaultSuperAdminEmail/defaultSuperAdminPassword) - Default structure definitions, search parameters, and value sets
This only runs once — subsequent startups detect that the seed data exists and skip it.