A React SPA built with Medplum's React component library that lets you explore biomedical research data from fhir-aggregator.org.
- 🔬 Browse ResearchStudies from TCGA, GDC, HTAN, ICGC, GTEx, and more
- 🔍 Search studies by identifier (e.g.
TCGA-BRCA) with quick-pick buttons - 📋 Study detail view: description, focus, keywords, subjects, specimens
- 🧬 ResearchSubject and Specimen tables with drill-through navigation
- 🗃️ Raw FHIR JSON viewer
- ✏️ Local create, update, and delete overlay for read-only servers
- 🔌 CRUD provider plugin contract (
CrudProvider) for swapping local storage with a custom backend - 🌐 Server selector — swap FHIR base at runtime
- 🚀 Vite dev proxy avoids browser CORS restrictions in development
npm install
npm run dev # → http://localhost:5173A production-oriented container setup is available in Dockerfile.
Build and run:
docker build -t resource-editor:latest .
docker run --rm --name resource-editor -p 4173:80 resource-editor:latestThen open http://localhost:4173.
Important: the container listens on port 80, so host mapping must be -p <host-port>:80.
If you previously started it with the wrong mapping (for example -p 4173:4173), restart with:
docker rm -f resource-editor
docker run --rm --name resource-editor -p 4173:80 resource-editor:latestBuild-time and runtime environment variables are documented in docs/docker.md.
src/
├── main.tsx # App bootstrap: MedplumProvider + MantineProvider
├── App.tsx # Router / route definitions
├── fhirClient.ts # Thin fetch wrapper + CRUD overlay application
├── localCrudStore.ts # CRUD provider contract + active provider registry
├── plugins/
│ └── customBackendCrudProvider.ts # Template plugin for backend CRUD persistence
├── components/
│ ├── AppLayout.tsx # AppShell header + server switcher
│ └── StudyCard.tsx # ResearchStudy summary card
└── pages/
├── HomePage.tsx # Paginated study browser with search
├── StudyDetailPage.tsx # 4-tab study detail (overview/subjects/specimens/raw)
└── ResourceDetailPage.tsx # Generic FHIR resource drillthrough
FHIR_BASE = https://google-fhir.fhir-aggregator.org
GET /ResearchStudy?_count=12&_sort=-_lastUpdated
GET /ResearchStudy?identifier=TCGA-BRCA
GET /ResearchStudy/{id}
GET /ResearchSubject?study=ResearchStudy/{id}&_count=100
GET /Specimen?_count=50
GET /{ResourceType}/{id}
| Component | Usage |
|---|---|
<MedplumProvider> |
Supplies MedplumClient context to the whole tree |
<ResourceTable value={resource} /> |
Renders any FHIR resource as a structured key/value table |
<CodeableConceptDisplay value={...} /> |
Renders FHIR CodeableConcept display text |
The Vite dev server proxies /fhir-proxy/* → https://google-fhir.fhir-aggregator.org/*
to avoid browser CORS restrictions during local development.
For production, deploy behind a reverse proxy or configure CORS on the FHIR server.
The app uses a provider pattern so CRUD persistence can be swapped without changing pages/components.
CrudProviderinterface insrc/localCrudStore.ts- Active provider registry via
setCrudProvider(...) - Default provider: local storage (
createLocalStorageCrudProvider()) - Custom backend template:
src/plugins/customBackendCrudProvider.ts
To switch providers during bootstrap, register your implementation in src/main.tsx before rendering:
import { setCrudProvider } from './localCrudStore';
import { createCustomBackendCrudProvider } from './plugins/customBackendCrudProvider';
setCrudProvider(createCustomBackendCrudProvider({
baseUrl: 'https://your-crud-api.example.com',
}));