Back to Blog

GraphQL Federation: Apollo Federation v2, Subgraphs, and Supergraph Design

Build a federated GraphQL architecture with Apollo Federation v2 — subgraph design, entity references, @key directives, supergraph schema composition, router co

Viprasol Tech Team
June 6, 2026
13 min read

GraphQL Federation: Apollo Federation v2, Subgraphs, and Supergraph Design

GraphQL Federation solves the organizational problem of GraphQL at scale: multiple teams each own their slice of the schema, and a router composes them into a unified API that clients query as if it were one service.

Without federation, a single GraphQL server forces all teams to coordinate schema changes through a bottleneck. With federation, the users team owns the User type, the orders team owns Order, and the router stitches them together at query time.


When Federation Makes Sense

Federation adds complexity. Don't use it until you have the problem it solves:

Use It WhenAvoid When
Multiple teams own different parts of the schemaSingle team owns everything
Services already deployed independentlyStarting a new project
You have > 3 backend servicesMonolith or 2 services
Teams deploying schema changes block each otherSchema changes are coordinated easily
Different services need independent scalingAll services scale together

For most SaaS products with < 5 engineers, a single GraphQL server is simpler and correct. Federation is for when you've outgrown it.


Core Concepts

Supergraph: The unified schema clients query against
Subgraph: An individual GraphQL service that owns part of the schema
Router: Apollo Router — composes subgraph schemas, routes queries
Entity: A type that can be referenced and extended across subgraphs

How a federated query works:

Client → Apollo Router
Router plans the query:
  - Fetch User.id, User.name from Users Subgraph
  - Fetch Order.id, Order.total from Orders Subgraph using User.id
  - Merge results and return unified response

🌐 Looking for a Dev Team That Actually Delivers?

Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 100+ projects.

  • React, Next.js, Node.js, TypeScript — production-grade stack
  • Fixed-price contracts — no surprise invoices
  • Full source code ownership from day one
  • 90-day post-launch support included

Building a Subgraph

Each subgraph is an independent GraphQL server with @apollo/subgraph installed:

// services/users/src/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';

const typeDefs = gql`
  extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])

  type User @key(fields: "id") {
    id: ID!
    email: String!
    name: String!
    createdAt: String!
  }

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

  type Mutation {
    updateUserName(name: String!): User!
  }
`;

const resolvers = {
  User: {
    // Reference resolver: called when other subgraphs need to resolve a User by ID
    __resolveReference(reference: { id: string }) {
      return getUserById(reference.id);
    },
  },
  Query: {
    user: (_: unknown, { id }: { id: string }) => getUserById(id),
    me: (_: unknown, __: unknown, context: { userId: string }) =>
      getUserById(context.userId),
  },
  Mutation: {
    updateUserName: async (
      _: unknown,
      { name }: { name: string },
      context: { userId: string }
    ) => {
      return updateUser(context.userId, { name });
    },
  },
};

export const schema = buildSubgraphSchema({ typeDefs, resolvers });
// services/users/src/server.ts
import Fastify from 'fastify';
import { ApolloServer } from '@apollo/server';
import fastifyApollo from '@as-integrations/fastify';
import { schema } from './schema';

const apollo = new ApolloServer({ schema });
await apollo.start();

const app = Fastify();
await app.register(fastifyApollo(apollo));

await app.listen({ port: 4001 });
console.log('Users subgraph running at http://localhost:4001/graphql');

The Orders Subgraph: Extending Another Service's Type

The Orders subgraph references User (defined in the Users subgraph) without importing its full definition:

// services/orders/src/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';

const typeDefs = gql`
  extend schema
    @link(url: "https://specs.apollo.dev/federation/v2.0",
          import: ["@key", "@external", "@requires"])

  # Reference the User type from Users subgraph
  # Only declare fields this subgraph needs to use
  type User @key(fields: "id") {
    id: ID! @external
    orders: [Order!]!  # Orders subgraph adds this field to User
  }

  type Order @key(fields: "id") {
    id: ID!
    userId: ID!
    user: User!
    items: [OrderItem!]!
    totalCents: Int!
    status: OrderStatus!
    createdAt: String!
  }

  type OrderItem {
    productId: ID!
    quantity: Int!
    priceCents: Int!
  }

  enum OrderStatus {
    PENDING
    PROCESSING
    SHIPPED
    DELIVERED
    CANCELLED
  }

  type Query {
    order(id: ID!): Order
    myOrders: [Order!]!
  }
`;

const resolvers = {
  User: {
    orders: (user: { id: string }) => getOrdersByUserId(user.id),
  },
  Order: {
    __resolveReference: (ref: { id: string }) => getOrderById(ref.id),
    user: (order: { userId: string }) => ({ __typename: 'User', id: order.userId }),
  },
  Query: {
    order: (_: unknown, { id }: { id: string }) => getOrderById(id),
    myOrders: (_: unknown, __: unknown, ctx: { userId: string }) =>
      getOrdersByUserId(ctx.userId),
  },
};

export const schema = buildSubgraphSchema({ typeDefs, resolvers });

Now clients can query:

query {
  me {
    name
    email
    orders {          # Resolved by Orders subgraph
      id
      totalCents
      status
    }
  }
}

The router fetches me from Users, then fetches orders from Orders using the user's id — transparent to the client.


🚀 Senior Engineers. No Junior Handoffs. Ever.

You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.

  • MVPs in 4–8 weeks, full platforms in 3–5 months
  • Lighthouse 90+ performance scores standard
  • Works across US, UK, AU timezones
  • Free 30-min architecture review, no commitment

Apollo Router Configuration

Apollo Router (Rust-based, replaces Apollo Gateway) handles composition and routing:

# router.yaml
supergraph:
  path: /graphql

# Subgraph definitions (in managed federation, this comes from Apollo Studio)
# For local development, use rover to compose the supergraph:

health_check:
  enabled: true
  path: /health

sandbox:
  enabled: true  # Apollo Sandbox UI at /

telemetry:
  tracing:
    otlp:
      endpoint: http://otel-collector:4317

cors:
  origins:
    - https://app.yourproduct.com
  allow_credentials: true

authentication:
  router:
    jwt:
      jwks:
        - url: https://your-tenant.auth0.com/.well-known/jwks.json

authorization:
  preview_directives:
    enabled: true
# Compose supergraph schema locally using Rover CLI
brew install rover

# rover.yaml — subgraph registry
subgraphs:
  users:
    routing_url: http://users-service:4001/graphql
    schema:
      file: ./services/users/schema.graphql
  orders:
    routing_url: http://orders-service:4002/graphql
    schema:
      file: ./services/orders/schema.graphql

# Compose and validate
rover supergraph compose --config rover.yaml > supergraph.graphql

# Start the router with composed schema
./router --config router.yaml --supergraph supergraph.graphql

Schema Design Best Practices

@key field selection:

# ✅ Use stable, immutable identifiers as @key
type User @key(fields: "id") {
  id: ID!
}

# ❌ Don't use mutable fields as @key — changes break federation
type User @key(fields: "email") {  # Email can change
  email: String!
}

# ✅ Compound keys for junction tables
type OrderItem @key(fields: "orderId productId") {
  orderId: ID!
  productId: ID!
}

@shareable for types owned by multiple subgraphs:

# Money type is simple and defined in multiple subgraphs
type Money @shareable {
  amountCents: Int!
  currency: String!
}

Avoiding N+1 in subgraph resolvers:

// Use DataLoader to batch reference resolutions
import DataLoader from 'dataloader';

const userLoader = new DataLoader<string, User>(async (userIds) => {
  const users = await db.users.findMany({
    where: { id: { in: [...userIds] } }
  });
  // Return in same order as input IDs
  return userIds.map(id => users.find(u => u.id === id) ?? new Error(`User ${id} not found`));
});

// In resolver
User: {
  __resolveReference: ({ id }: { id: string }) => userLoader.load(id),
},

Migrating from Schema Stitching

If you're on schema stitching (the predecessor to federation), migration steps:

1. Add @apollo/subgraph to each service
2. Add `extend schema @link(...)` to each service's type definitions  
3. Add `@key` directives to types used across services
4. Implement `__resolveReference` for each entity
5. Replace `makeRemoteExecutableSchema + mergeSchemas` with Apollo Router
6. Test with `rover subgraph check` before deploying
7. Run old and new gateway in parallel, compare query results
8. Cut over traffic, remove old gateway

Cost and Performance Comparison

SetupLatency OverheadInfrastructureBest For
Apollo Gateway (JS)15–30msNode.js serviceSmaller scale, JS team
Apollo Router (Rust)1–5msSingle binaryProduction, high scale
Schema stitching20–40msNode.js serviceLegacy; don't start new

Apollo Router processes ~30,000 requests/second per instance on a t3.medium. For most SaaS products, a single router instance is sufficient.


Working With Viprasol

We design and implement GraphQL federation architectures — subgraph design, entity modeling, Apollo Router configuration, migration from schema stitching, and DataLoader patterns for efficient data fetching.

Talk to our team about GraphQL architecture and API design.


See Also

Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need a Modern Web Application?

From landing pages to complex SaaS platforms — we build it all with Next.js and React.

Free consultation • No commitment • Response within 24 hours

Viprasol · Web Development

Need a custom web application built?

We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.