Install
$ agentstack add skill-j4flmao-agent-skills-graphql-federation ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
GraphQL Federation
Purpose
Design and implement GraphQL Federation: compose multiple subgraphs into a unified supergraph, manage distributed GraphQL architecture, and scale GraphQL across teams. Make informed build-vs-buy decisions between Apollo Federation and alternative distributed graph strategies.
Workflow
Federation Architecture
Supergraph (Apollo Router / Gateway)
├── Subgraph A (Users service) — @key(fields: "id")
├── Subgraph B (Orders service) — @key(fields: "id") @extends User
├── Subgraph C (Reviews service) — @key(fields: "id") @extends User
└── Subgraph D (Inventory service) — standalone
Apollo Federation vs. GraphQL Mesh Decision Tree
Use this decision framework when choosing between Apollo Federation and GraphQL Mesh:
| Criterion | Apollo Federation | GraphQL Mesh | |-----------|-----------------|--------------| | Schema ownership | Each subgraph owns its schema | Sources are existing APIs (REST, gRPC, SOAP, etc.) | | Team topology | Multiple teams own separate subgraphs | Single team integrating existing backends | | Source types | Native GraphQL only | Any source (REST, OpenAPI, gRPC, SQL, SOAP, etc.) | | Composition model | Static composition via Rover CLI | Runtime schema stitching | | Gateway | Apollo Router (Rust) or Gateway (Node.js) | Envelop, Yoga, or custom | | Federation directives | @key, @extends, @requires, @provides, @shareable | Transformers, handlers, mesh config | | Entity resolution | Built-in via __resolveReference | Manual stitching resolvers | | Query planning | Automatic query planner | Manual or custom merge config | | Performance | Optimized Rust router, plan caching | Depends on underlying gateway | | Ecosystem maturity | Mature (Apollo GraphOS, Studio) | Growing (The Guild ecosystem) | | When to choose | Greenfield GraphQL with multiple teams | Wrapping legacy/heterogeneous backends |
Decision flow:
- Are all your sources already GraphQL? → Apollo Federation
- Do you need to wrap REST/gRPC/SQL backends? → GraphQL Mesh
- Do you have 3+ teams owning separate domains? → Apollo Federation
- Is your primary goal API unification of legacy systems? → GraphQL Mesh
- Hybrid approach: Use Mesh to convert REST → GraphQL, then compose those as subgraphs via Federation
Federation Directives (Federation v2)
| Directive | Purpose | Example | |-----------|---------|---------| | @key | Primary key for entity | @key(fields: "id") | | @extends | Extend type from another subgraph (implied in v2) | type User @key(fields: "id") | | @external | Field defined in another subgraph (implied in v2) | id: ID! | | @requires | Field requires data from another subgraph | @requires(fields: "shippingZip") | | @provides | Field provides data to other subgraphs | @provides(fields: "name") | | @shareable | Field can be resolved by multiple subgraphs | @shareable | | @override | Migrate field resolution between subgraphs | @override(from: "inventory") | | @inaccessible | Hide field from supergraph | @inaccessible | | @composeDirective | Propagate directive to supergraph | @composeDirective(name: "@authorized") | | @interfaceObject | Expose interface fields on entity | @interfaceObject |
Subgraph Schema Example (Federation v2)
# Users subgraph
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
# Orders subgraph (extends User — no @extends needed in v2)
type User @key(fields: "id") {
id: ID!
orders: [Order!]!
}
type Order @key(fields: "id") {
id: ID!
userId: ID!
total: Float!
status: OrderStatus!
}
Entity Resolution Deep Dive
Each subgraph that extends an entity must implement __resolveReference:
// Users subgraph — entity origin
const resolvers = {
User: {
__resolveReference(ref, context) {
return context.dataSources.users.findById(ref.id);
},
},
};
// Orders subgraph — entity extension
const resolvers = {
User: {
__resolveReference(ref, context) {
// Return just enough to resolve orders
return { id: ref.id };
},
orders(parent, _, context) {
return context.dataSources.orders.findByUserId(parent.id);
},
},
};
Resolution flow:
- Router receives query spanning multiple subgraphs
- Router sends
_entitiesquery to Orders subgraph with representations[{"__typename": "User", "id": "1"}] - Subgraph calls
__resolveReferencewith each representation - Subgraph returns the entity with only the fields requested from it
- Router merges fields from all subgraph responses into a unified result
@requires Resolution Flow
# Shipping subgraph needs weight from another subgraph
type Product @key(fields: "id") {
id: ID!
weight: Int @external
shippingCost: Float @requires(fields: "weight")
}
- Router fetches
weightfrom the subgraph that owns it - Router includes
weightin the representation sent to Shipping subgraph - Shipping subgraph receives
{"__typename": "Product", "id": "1", "weight": 10} - Computes
shippingCostusing the pre-fetchedweight
Supergraph Composition Pipeline
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Subgraph A │ │ Subgraph B │ │ Subgraph C │
│ Schema │ │ Schema │ │ Schema │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
▼
┌───────────────┐
│ Composition │ rover supergraph compose
│ Engine │
└───────┬───────┘
▼
┌───────────────┐
│ Supergraph │ unified schema sent to router
│ Schema │
└───────────────┘
# supergraph.yaml
federation_version: 2
subgraphs:
accounts:
routing_url: http://accounts:4001/graphql
schema:
file: ./schemas/accounts.graphql
products:
routing_url: http://products:4002/graphql
schema:
file: ./schemas/products.graphql
orders:
routing_url: http://orders:4003/graphql
schema:
file: ./schemas/orders.graphql
rover supergraph compose --config ./supergraph.yaml > supergraph.graphql
Schema Evolution & Breaking Change Detection
# Check backward compatibility before deploying
rover subgraph check my-graph@current \
--schema ./updated-accounts.graphql \
--name accounts
# If check passes, publish the new schema
rover subgraph publish my-graph@current \
--schema ./updated-accounts.graphql \
--name accounts \
--routing-url http://accounts:4001/graphql
What rover subgraph check validates:
- Field/type removals
- Argument changes
- @key directive changes
- Value type to entity conversion
- Enum value additions/removals
Federated Tracing & Observability
OpenTelemetry in Apollo Router
# router.yaml — federated tracing
telemetry:
tracing:
otlp:
endpoint: http://otel-collector:4318
protocol: http
propagation:
context: cloudtrace
# Forward trace context to subgraphs
forward:
- "traceparent"
- "x-cloud-trace-context"
sampling:
default: 0.1
subgraphs:
accounts: 1.0 # Trace all accounts requests
reviews: 0.01 # Sample reviews at 1%
What federated tracing reveals:
- Query plan generation — time spent planning the multi-subgraph fetch
- Per-subgraph latency — which subgraph is the bottleneck
- Fetch boundary count — number of subgraph hops per query
- Entity resolution — time spent in
__resolveReference - Representation passing — overhead of data transfer between services
Apollo Studio Integration
# router.yaml
telemetry:
apollo:
graphs:
- graph_ref: my-graph@current
key: ${APOLLO_KEY}
field_usage: true # Track field-level usage stats
operation_counts: true # Track operation frequency
Studio provides:
- Field usage heatmaps — which fields are most requested
- Operation traces — waterfall view across subgraphs
- Schema checks — breaking change detection in CI
- Cost analysis — query complexity scoring
Security in Federated Architecture
Layered Security Model
Client → Router (authN + rate limit) → Subgraph (authZ + validation)
Router-Level Authentication (JWT)
# router.yaml
authentication:
jwt:
jwks_urls:
- https://auth.example.com/.well-known/jwks.json
issuer: https://auth.example.com/
audiences:
- my-api
headers:
all:
request:
- propagate:
matching: .*
- insert:
name: "x-user-id"
value: "{{ authentication.jwt.sub }}"
- insert:
name: "x-user-roles"
value: "{{ authentication.jwt.claims.roles }}"
Subgraph-Level Authorization
// Each subgraph independently validates permissions
const resolvers = {
Query: {
userOrders: async (_, { userId }, { userId: authUserId, roles }) => {
if (authUserId !== userId && !roles.includes('admin')) {
throw new GraphQLError('Forbidden', {
extensions: { code: 'FORBIDDEN' },
});
}
return db.orders.findByUserId(userId);
},
},
User: {
__resolveReference(ref, { userId, roles }) {
// Guard entity resolution — don't leak existence
if (!userId) return null;
return db.users.findById(ref.id);
},
},
};
Protecting Against GraphQL-Specific Attacks
| Attack | Mitigation | Config | |--------|-----------|--------| | Deeply nested queries | Depth limiting | max_depth: 10 | | Query aliasing abuse | Alias limiting | max_aliases: 15 | | Entity list bombing | Max entities per request | max_entities_per_request: 100 | | Introspection scraping | Disable in production | introspection: false | | Costly queries | Cost analysis | demand_control.strategy: cost_bound |
# router.yaml — demand control
demand_control:
strategy: cost_bound
list_cost: 1
object_cost: 2
scoring:
max_cost: 1000
max_depth: 10
reject_on_limit_exceeded: true
rate_limiting:
global:
capacity: 1000
time_window: 60s
per_user:
capacity: 100
time_window: 60s
Production Considerations
Traffic Shaping Per Subgraph
traffic_shaping:
all:
timeout: 30s
compression: true
http2:
keepalive_interval: 30s
keepalive_timeout: 10s
subgraphs:
accounts:
timeout: 5s
retry:
max_retries: 3
base_interval: 100ms
reviews:
timeout: 3s
circuit_breaker:
error_threshold: 0.5
request_volume_threshold: 20
sleep_window: 30s
half_open_requests: 5
inventory:
timeout: 10s
retry:
max_retries: 2
Query Plan Caching
query_planning:
cache:
enabled: true
size: 10000
ttl: 3600s
experimental_plans: false
incremental_delivery:
enable_single_entity: true
Warm the cache on deploy:
// Pre-warm common query plans
const commonQueries = [
`query { me { id name } }`,
`query { products(first: 10) { id name price } }`,
`query { order(id: "hot") { id status total } }`,
];
await Promise.all(commonQueries.map(q => router.execute(q)));
Blue-Green Supergraph Deployment
# 1. Compose new supergraph
rover supergraph compose --config ./supergraph.v2.yaml --output supergraph.v2.graphql
# 2. Deploy to staging router
cp supergraph.v2.graphql /etc/apollo/supergraph.staging.graphql
# 3. Health check staging router
curl -f http://localhost:4001/.well-known/apollo/server-health
# 4. Promote to production
cp supergraph.v2.graphql /etc/apollo/supergraph.graphql
# 5. Reload production router
kill -HUP $(cat /var/run/apollo-router.pid)
Performance Budgets
performance_budget:
max_fetch_count: 5 # Max subgraph hops per query
max_query_depth: 8 # Max nesting depth
max_latency_p99_ms: 500 # P99 latency across all subgraphs
max_cost_per_query: 100 # Cost analysis limit
Reducing Fetch Boundaries with @provides
# Products subgraph: users browsing products don't hit accounts
type Query {
topProducts: [Product!]!
}
extend type Product @key(fields: "id") {
id: ID! @external
name: String! @external @provides(fields: "name")
price: Float! @provides(fields: "currency")
}
DataLoader Across Subgraphs
// Batch-load entities to avoid N+1 resolution calls
class UserLoader {
private batch = new Map>();
load(id: string): Promise {
if (!this.batch.has(id)) {
this.batch.set(id, this.fetchBatch());
}
return this.batch.get(id)!;
}
private async fetchBatch(): Promise {
const ids = [...this.batch.keys()];
const users = await db.users.findByIds(ids);
for (const user of users) {
this.batch.set(user.id, Promise.resolve(user));
}
}
}
Testing Federated Graphs
Unit Test: Subgraph Entity Resolution
describe('Accounts Subgraph', () => {
it('resolves User entity by key', async () => {
const result = await subgraph.executeQuery(`
query ($representations: [_Any!]!) {
_entities(representations: $representations) {
... on User { id name email }
}
}
`, {
representations: [{ __typename: 'User', id: '1' }],
});
expect(result.data._entities[0].name).toBe('Alice');
});
});
Integration Test: Cross-Subgraph Query
describe('Supergraph: User with Orders', () => {
it('resolves fields across subgraphs', async () => {
const query = `
query { user(id: "1") { name orders { total status } } }
`;
const result = await gateway.execute(query);
expect(result.data.user.name).toBe('Alice');
expect(result.data.user.orders).toHaveLength(3);
});
});
Contract Testing Between Subgraphs
// Each subgraph publishes its schema contract
// CI validates that contracts remain compatible
describe('Contracts', () => {
it('accounts subgraph schema is valid', async () => {
const schema = fs.readFileSync('./schemas/accounts.graphql', 'utf8');
const errors = await validateSchema(schema);
expect(errors).toHaveLength(0);
});
it('orders extension of User is compatible', () => {
// Verify @key fields match across subgraphs
const userKey = extractKey(accountsSchema, 'User');
const orderUserKey = extractKey(ordersSchema, 'User');
expect(userKey).toEqual(orderUserKey);
});
});
Common Composition Errors
| Error | Cause | Fix | |-------|-------|-----| | ENUM_MISMATCH | Enum values differ between subgraphs | Align enum definitions | | TYPE_MISMATCH | Same name used for type vs interface | Unify type kind | | EXTERNAL_MISSING | Referenced field not @external | Mark field as external | | KEY_MISSING | Type extended but no @key in origin | Add @key to origin | | REQUIRES_MISSING | @requires field unavailable | Ensure field is resolvable | | DUPLICATE_FIELD | Field defined in multiple subgraphs without @shareable | Add @shareable |
Migration Guide: Federation 1 → Federation 2
| Federation 1 | Federation 2 | |-------------|-------------| | @extends | Unnecessary (all type extensions are implicit) | | @external | Unnecessary (fields owned elsewhere assumed external) | | gateway.js | @apollo/gateway v2+ handles automatically | | Composition | rover supergraph compose with federation_version: 2 |
Steps:
- Update
federation_versionto2in supergraph config - Remove all
@extendsand@externaldirectives from subgraph schemas - Update to
@apollo/gateway@^2.0or deploy Apollo Router - Run rover supergraph compose to validate
- Deploy ne
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: j4flmao
- Source: j4flmao/agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.