From dc2248a11bd13008702b1abec12834d5ffb7d163 Mon Sep 17 00:00:00 2001 From: Andrey Listopadov Date: Fri, 16 Jan 2026 18:48:13 +0300 Subject: [PATCH] add example for apollo federation support --- README.md | 1 + .../apollo-graphql-federation/README.md | 145 ++++++++++++++++++ .../docker-compose.yaml | 75 +++++++++ .../gateway/index.js | 28 ++++ .../gateway/package.json | 10 ++ 5 files changed, 259 insertions(+) create mode 100644 aidbox-integrations/apollo-graphql-federation/README.md create mode 100644 aidbox-integrations/apollo-graphql-federation/docker-compose.yaml create mode 100644 aidbox-integrations/apollo-graphql-federation/gateway/index.js create mode 100644 aidbox-integrations/apollo-graphql-federation/gateway/package.json diff --git a/README.md b/README.md index c45c512..7569096 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ A collection of examples on top of Aidbox FHIR platform - [BedaEMR integration](aidbox-integrations/BedaEmr/) - [FHIR Analytics](aidbox-integrations/fhir-analytics/) - [Real-Time Analytics with Aidbox, ClickHouse, and Superset](aidbox-integrations/clickhouse-superset/) +- [GraphQL Federation](aidbox-integrations/apollo-graphql-federation/) ## Aidbox Features diff --git a/aidbox-integrations/apollo-graphql-federation/README.md b/aidbox-integrations/apollo-graphql-federation/README.md new file mode 100644 index 0000000..bdad21b --- /dev/null +++ b/aidbox-integrations/apollo-graphql-federation/README.md @@ -0,0 +1,145 @@ +--- +features: [Graphql, Federation, Apollo] +languages: [YAML, JavaScript] +--- +# Apollo Federation with Aidbox + +Access Aidbox's FHIR GraphQL API through an Apollo Gateway via [Apollo Federation v2](https://www.apollographql.com/docs/federation/). + +## Overview + +Apollo Federation allows you to compose multiple GraphQL services (subgraphs) into a single unified API (supergraph). +With Aidbox's federation support enabled, Aidbox exposes its FHIR GraphQL schema as a federation-compatible subgraph, which can be consumed by Apollo Gateway. + +## Prerequisites + +1. Docker +2. Cloned repository: [Github: Aidbox/examples](https://github.com/Aidbox/examples/tree/main) +3. Working directory: `apollo-graphql-federation` +4. Aidbox license key + +``` +git clone https://github.com/Aidbox/examples.git +cd examples/aidbox-integrations/apollo-graphql-federation +``` + +## Run Aidbox and Apollo gateway + +```bash +AIDBOX_LICENSE="your aidbox license key" docker compose up -d +``` + +## How Federation Works + +When federation is enabled (`BOX_MODULE_GRAPHQL_FEDERATION_SUPPORT=true`), Aidbox: + +1. **Adds Federation 2 directives** to the GraphQL schema: + - `@link` - Declares this as a Federation 2 schema + - `@key(fields: "id")` - Marks FHIR resource types as entities + - `@shareable` - Allows types to be resolved by multiple subgraphs + +2. **Exposes `_service` query** that returns the GraphQL SDL with federation directives + +3. **Implements `_entities` resolver** for resolving entity references + +## Usage + +### Creating Test Data + +Create a few patients in Aidbox using the REST Console: + +{% code title="Request" %} +```http +PUT /fhir/Patient/pt-1 +content-type: text/yaml +accept: text/yaml + +id: pt-1 +name: + - family: Smith + given: [John] +birthDate: '1990-01-15' +``` +{% endcode %} + +{% code title="Request" %} +```http +PUT /fhir/Patient/pt-2 +content-type: text/yaml +accept: text/yaml + +id: pt-2 +name: + - family: Johnson + given: [Jane] +birthDate: '1985-06-20' +``` +{% endcode %} + +### Querying via Apollo Gateway + +You can query Aidbox through the Apollo Gateway at `http://localhost:4000`. +You can also open this URL in a browser to access Apollo Sandbox - an interactive GraphQL IDE for exploring the schema and running queries. + +#### Query a Single Patient + +{% code title="Request" %} +```bash +curl -s -X POST http://localhost:4000 \ + -H "Content-Type: application/json" \ + -u "root:secret" \ + -d '{"query": "{ Patient(id: \"pt-1\") { id name { family given } birthDate } }"}' +``` +{% edncode %} + +{% code title="Response" %} +``` json +{ + "data": { + "Patient": { + "id": "pt-1", + "name": [{ + "family": "Smith", + "given": ["John"] + }], + "birthDate": "1990-01-15" + } + } +} +``` +{% endcode %} + +#### Query All Patients + +{% code title="Request" %} +```bash +curl -s -X POST http://localhost:4000 \ + -H "Content-Type: application/json" \ + -u "root:secret" \ + -d '{"query": "{ PatientList { id name { family given } birthDate } }"}' +``` +{% endcode %} + +{% code title="Response" %} +```json +{ + "data": { + "PatientList": [{ + "id": "pt-1", + "name": [{ + "family": "Smith", + "given": ["John"] + }], + "birthDate": "1990-01-15" + }, { + "id": "pt-2", + "name": [{ + "family": "Johnson", + "given": ["Jane"] + }], + "birthDate": "1985-06-20" + }] + } +} +``` +{% endcode %} diff --git a/aidbox-integrations/apollo-graphql-federation/docker-compose.yaml b/aidbox-integrations/apollo-graphql-federation/docker-compose.yaml new file mode 100644 index 0000000..d3ce41f --- /dev/null +++ b/aidbox-integrations/apollo-graphql-federation/docker-compose.yaml @@ -0,0 +1,75 @@ +volumes: + postgres_data: {} +services: + postgres: + image: docker.io/library/postgres:18 + volumes: + - postgres_data:/var/lib/postgresql/18/docker:delegated + command: + - postgres + - -c + - shared_preload_libraries=pg_stat_statements + environment: + POSTGRES_USER: aidbox + POSTGRES_PORT: '5432' + POSTGRES_DB: aidbox + POSTGRES_PASSWORD: postgres + + aidbox: + image: docker.io/healthsamurai/aidboxone:edge + pull_policy: always + depends_on: + - postgres + ports: + - "8080:8080" + environment: + BOX_MODULE_GRAPHQL_FEDERATION_SUPPORT: "true" + AIDBOX_LICENSE: "$AIDBOX_LICENSE" + BOX_ADMIN_PASSWORD: password + BOX_BOOTSTRAP_FHIR_PACKAGES: hl7.fhir.r4.core#4.0.1 + BOX_COMPATIBILITY_VALIDATION_JSON__SCHEMA_REGEX: '#{:fhir-datetime}' + BOX_DB_DATABASE: aidbox + BOX_DB_HOST: postgres + BOX_DB_PASSWORD: postgres + BOX_DB_PORT: '5432' + BOX_DB_USER: aidbox + BOX_FHIR_BUNDLE_EXECUTION_VALIDATION_MODE: limited + BOX_FHIR_COMPLIANT_MODE: 'true' + BOX_FHIR_CORRECT_AIDBOX_FORMAT: 'true' + BOX_FHIR_CREATEDAT_URL: https://aidbox.app/ex/createdAt + BOX_FHIR_SCHEMA_VALIDATION: 'true' + BOX_FHIR_SEARCH_AUTHORIZE_INLINE_REQUESTS: 'true' + BOX_FHIR_SEARCH_CHAIN_SUBSELECT: 'true' + BOX_FHIR_SEARCH_COMPARISONS: 'true' + BOX_FHIR_TERMINOLOGY_ENGINE: hybrid + BOX_FHIR_TERMINOLOGY_ENGINE_HYBRID_EXTERNAL_TX_SERVER: https://tx.health-samurai.io/fhir + BOX_FHIR_TERMINOLOGY_SERVICE_BASE_URL: https://tx.health-samurai.io/fhir + BOX_MODULE_SDC_STRICT_ACCESS_CONTROL: 'true' + BOX_ROOT_CLIENT_SECRET: secret + BOX_SEARCH_INCLUDE_CONFORMANT: 'true' + BOX_SECURITY_AUDIT_LOG_ENABLED: 'true' + BOX_SECURITY_DEV_MODE: 'true' + BOX_SETTINGS_MODE: read-write + BOX_WEB_BASE_URL: http://localhost:8080 + BOX_WEB_PORT: 8080 + healthcheck: + test: curl -f http://localhost:8080/health || exit 1 + interval: 5s + timeout: 5s + retries: 90 + start_period: 30s + + apollo-gateway: + image: node:20-alpine + ports: + - "4000:4000" + working_dir: /app + volumes: + - ./gateway:/app + command: sh -c "npm install && node index.js" + environment: + AIDBOX_URL: http://aidbox:8080/$$graphql + AIDBOX_ROOT_SECRET: secret + depends_on: + aidbox: + condition: service_healthy diff --git a/aidbox-integrations/apollo-graphql-federation/gateway/index.js b/aidbox-integrations/apollo-graphql-federation/gateway/index.js new file mode 100644 index 0000000..3d6a679 --- /dev/null +++ b/aidbox-integrations/apollo-graphql-federation/gateway/index.js @@ -0,0 +1,28 @@ +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; +import { ApolloGateway, IntrospectAndCompose, RemoteGraphQLDataSource } from '@apollo/gateway'; + +const authHeader = 'Basic ' + Buffer.from(`root:${process.env.AIDBOX_ROOT_SECRET}`).toString('base64'); + +class AuthenticatedDataSource extends RemoteGraphQLDataSource { + willSendRequest({ request }) { + request.http.headers.set('Authorization', authHeader); + } +} + +const gateway = new ApolloGateway({ + supergraphSdl: new IntrospectAndCompose({ + subgraphs: [{ name: 'aidbox', url: process.env.AIDBOX_URL }], + }), + buildService({ url }) { + return new AuthenticatedDataSource({ url }); + }, +}); + +const server = new ApolloServer({ gateway }); + +const { url } = await startStandaloneServer(server, { + listen: { port: 4000, host: '0.0.0.0' }, +}); + +console.log(`Apollo Gateway ready at ${url}`); diff --git a/aidbox-integrations/apollo-graphql-federation/gateway/package.json b/aidbox-integrations/apollo-graphql-federation/gateway/package.json new file mode 100644 index 0000000..c069ac9 --- /dev/null +++ b/aidbox-integrations/apollo-graphql-federation/gateway/package.json @@ -0,0 +1,10 @@ +{ + "name": "apollo-gateway-aidbox", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@apollo/gateway": "^2.9.3", + "@apollo/server": "^4.11.3", + "graphql": "^16.9.0" + } +}