Client Area
GraphQL APIsAdvanced

GraphQL Federation — Multi-Service Schemas with Apollo Federation

ByDomain India Team·DomainIndia Engineering
4 min readPublished 24 Apr 2026Updated 23 Jun 2026204 views

In this article

  • 1When to federate
  • 2The architecture
  • 3Apollo Federation v2
  • 4Step 1 — Design subgraphs
  • 5Step 2 — Subgraph server (Node.js example)

GraphQL Federation — Multi-Service Schemas with Apollo Federation

TL;DR
When your app grows past 5-10 teams, a single monolithic GraphQL schema becomes a bottleneck. Federation lets each team own a subgraph; a gateway composes them into a unified graph. This guide covers Apollo Federation v2 setup, subgraph design, gateway deployment on DomainIndia VPS, and common pitfalls.

When to federate

Don't start here. Federation adds operational complexity.

Stay monolithic when:

  • Single team owns the schema
  • <20 engineers touching GraphQL
  • No clear domain boundaries

Federate when:

  • Multiple teams need autonomy over their slice of the graph
  • Domains are genuinely separate (users, orders, inventory)
  • Monolith deploys are getting slow
  • Contributors step on each other's schemas

Most companies never need federation. Most that adopt it, adopt too early.

The architecture

Client (React / iOS / Android)
    │
    ▼
[Gateway / Router] ──► Subgraph: Users (Node.js)
    │                ──► Subgraph: Products (Python)
    │                ──► Subgraph: Orders (Go)
    │                ──► Subgraph: Reviews (PHP)
    │
    └── One endpoint, one unified schema
  • Each subgraph is a full GraphQL server managed by its team
  • Gateway queries subgraphs, composes responses
  • Subgraphs can extend each other's types
  • Gateway handles auth, rate limiting, caching

Apollo Federation v2

Install Apollo Gateway + Router:

bash
# Router (Rust, high performance — recommended for production)
curl -sSL https://router.apollo.dev/download/nix/latest | sh

Alternative: Apollo Gateway (Node.js — simpler for development).

Step 1 — Design subgraphs

Users subgraph:

graphql
# users/schema.graphql
type User @key(fields: "id") {
  id: ID!
  email: String!
  name: String!
  createdAt: String!
}

extend type Query {
  me: User
  user(id: ID!): User
}

Orders subgraph:

graphql
# orders/schema.graphql
type Order @key(fields: "id") {
  id: ID!
  total: Float!
  status: String!
  user: User!           # cross-subgraph reference
}

extend type User @key(fields: "id") {
  id: ID! @external     # declared in Users subgraph
  orders: [Order!]!     # new field on User owned by Orders subgraph
}

extend type Query {
  order(id: ID!): Order
}

@key marks a type as federatable. extend type adds fields to a type owned by another subgraph.

Step 2 — Subgraph server (Node.js example)

bash
npm install @apollo/subgraph graphql
typescript
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { buildSubgraphSchema } from '@apollo/subgraph';
import { readFileSync } from 'fs';
import { parse } from 'graphql';

const typeDefs = parse(readFileSync('./schema.graphql', 'utf-8'));

const resolvers = {
  Query: {
    me: async (_, __, { user }) => db.user.findById(user.id),
    user: async (_, { id }) => db.user.findById(id),
  },
  User: {
    __resolveReference: async (ref) => db.user.findById(ref.id),
  },
};

const server = new ApolloServer({
  schema: buildSubgraphSchema({ typeDefs, resolvers }),
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4001 },
  context: async ({ req }) => ({ user: decodeUser(req) }),
});

console.log(`Users subgraph at ${url}`);

__resolveReference — when the gateway has a {__typename: "User", id: "42"}, it asks the Users subgraph to resolve full User.

Step 3 — Gateway (Apollo Router)

router.yaml:

yaml
supergraph:
  listen: 0.0.0.0:4000
  path: /graphql

subgraphs:
  users:
    routing_url: http://users-service.internal:4001/graphql
  orders:
    routing_url: http://orders-service.internal:4002/graphql
  products:
    routing_url: http://products-service.internal:4003/graphql

cors:
  origins:
    - https://yourcompany.com

headers:
  all:
    request:
      - propagate:
          named: authorization    # forward auth header to subgraphs

rate_limiting:
  global:
    interval: 1s
    capacity: 100

Compose the supergraph SDL:

bash
rover supergraph compose --config supergraph.yaml > supergraph.graphql

Start router:

bash
router --config router.yaml --supergraph supergraph.graphql

Step 4 — Client queries

Clients hit the gateway endpoint only:

graphql
query MyDashboard {
  me {
    id
    name
    orders {         # owned by Orders subgraph
      id
      total
      status
    }
  }
}

The gateway orchestrates:

  1. Query Users for {id, name}
  2. Query Orders with the user ID for {id, total, status}
  3. Compose response

Client doesn't know there are multiple services.

Cross-subgraph relationships

Key entities

graphql
# Reviews subgraph
type Review @key(fields: "id") {
  id: ID!
  rating: Int!
  text: String!
  product: Product!
}

extend type Product @key(fields: "id") {
  id: ID! @external
  reviews: [Review!]!
}

Products subgraph doesn't know about Reviews. Reviews subgraph extends Product with a reviews field. The gateway routes product.reviews to the Reviews subgraph.

@requires and @provides

When Orders needs a User field that's NOT the key:

graphql
extend type User @key(fields: "id") {
  id: ID! @external
  email: String @external
}

type Order @key(fields: "id") {
  id: ID!
  notificationEmail: String @requires(fields: "email")
  user: User!
}

@requires(fields: "email") — before resolving notificationEmail, gateway fetches email from Users subgraph.

Deployment on DomainIndia

Each subgraph as its own systemd service on a VPS (or separate VPS).

Router as separate service.

VPS 1: Apollo Router       (:4000)
VPS 2: Users subgraph      (:4001) + Postgres-users
VPS 3: Orders subgraph     (:4002) + Postgres-orders
VPS 4: Products subgraph   (:4003) + Postgres-products

Internal network (Cloudflare Tunnel or private subnet) for subgraph traffic. Only router exposed publicly.

Auth in federation

JWT validated at router, claims forwarded to subgraphs:

yaml
# router.yaml
authentication:
  router:
    jwt:
      jwks:
        - url: https://auth.yourcompany.com/.well-known/jwks.json

Subgraphs trust the router's auth context — don't re-verify JWT per subgraph.

Caching

Apollo Router has query-plan caching:

yaml
supergraph:
  query_planning:
    cache:
      in_memory:
        limit: 512

Responses can be cached per-field with @cacheControl(maxAge: 60):

graphql
type Product @key(fields: "id") @cacheControl(maxAge: 60) {
  id: ID!
  name: String!
  price: Float! @cacheControl(maxAge: 10)    # more volatile
}

Monitoring

Apollo Studio (paid — free tier) gives visibility:

  • Subgraph latency
  • Error rates per field
  • Slow query hotspots
  • Schema usage (which fields are actually queried)

Alternative: self-hosted with OpenTelemetry → Grafana Tempo.

Migration from monolith

Gradual path:

  1. Start with a monolithic GraphQL schema on 1 server
  2. When a team needs autonomy, extract their types into a subgraph
  3. Add the first subgraph router between clients and the monolith
  4. Repeat: extract more subgraphs, monolith shrinks
  5. Eventually: pure federated architecture

Don't "big bang" migrate. Incremental with clear ownership.

Common pitfalls

FAQ

Q Apollo Federation or GraphQL stitching?

Stitching is legacy (Apollo v1 approach). Federation v2 is the modern standard. New projects use federation.

Q Hasura Remote Schemas instead of federation?

Hasura federates any GraphQL endpoint — simpler for mixed technology stacks. Apollo Federation gives more control (requires @key discipline).

Q Monolith or federate from day one?

Monolith. Federate only when 2+ teams independently evolve the schema.

Q Can I use Federation with a single subgraph?

Yes — even one subgraph + router gets you router features (rate limiting, auth, caching). Pragmatic starting point.

Q REST alternative to federation?

BFF pattern (Backend-for-Frontend) — one GraphQL API per client (web, mobile). Each BFF aggregates REST calls. Simpler ops than federation for some teams.

Host a federated GraphQL stack on multiple DomainIndia VPSes. Explore VPS plans

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket