---
# === IDENTITY ===
id: software/system-design/graphql-api-architecture/2026
canonical_question: "How do I architect a GraphQL API at scale?"
aliases:
  - "GraphQL API architecture design"
  - "scaling GraphQL API production"
  - "GraphQL federation vs schema stitching"
  - "GraphQL API design patterns at scale"
  - "how to design a scalable GraphQL API"
  - "GraphQL system design interview"
  - "GraphQL supergraph architecture"
  - "federated GraphQL microservices"
entity_type: software_reference
domain: software > system-design > graphql_api_architecture
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-02-23
confidence: 0.92
freshness: quarterly
version: 1.0
first_published: 2026-02-23

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "Apollo Federation v2 (2022) — incompatible with v1 @key/@requires syntax"
  next_review: 2026-08-22
  change_sensitivity: medium

# === CONSTRAINTS ===
constraints:
  - "Always implement query depth limiting and complexity analysis in production — unbounded GraphQL queries can cause exponential database load and denial-of-service"
  - "Never expose GraphQL introspection in production environments — it reveals your entire schema to attackers and enables automated exploitation"
  - "DataLoader (or equivalent batching) is mandatory for any resolver that fetches from a database or external service — without it, N+1 queries will destroy performance"
  - "Persisted queries or an allowlist of approved operations must be enforced in production to prevent arbitrary query injection and abuse"
  - "Federation gateway (Apollo Router / GraphQL Mesh) must be the only public entry point — never expose subgraph endpoints directly to clients"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Building a simple internal API with <5 entity types and one team"
    use_instead: "A single GraphQL monolith server (Apollo Server / graphql-yoga) without federation is sufficient"
  - condition: "Need to migrate an existing REST API to GraphQL"
    use_instead: "software/migrations/rest-to-graphql/2026"
  - condition: "Debugging N+1 query performance in an existing GraphQL API"
    use_instead: "software/debugging/graphql-n-plus-1-dataloader/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: scale
    question: "What is your expected scale (concurrent users and queries per second)?"
    type: choice
    options: ["Small (<1K users, <100 QPS)", "Medium (1K-50K users, 100-5K QPS)", "Large (50K-500K users, 5K-50K QPS)", "Very large (>500K users, >50K QPS)"]
  - key: team_structure
    question: "How many teams will contribute to the GraphQL schema?"
    type: choice
    options: ["Single team", "2-5 teams", "6-20 teams", ">20 teams"]
  - key: architecture_approach
    question: "Do you want a monolith GraphQL server or a federated architecture?"
    type: choice
    options: ["Monolith (single schema, single deployment)", "Federation (distributed subgraphs, unified gateway)", "Schema stitching (merge multiple schemas at gateway)", "Undecided — help me choose"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/system-design/graphql-api-architecture/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-23)"

# === RELATED UNITS ===
related_kos:
  depends_on:
    - id: "software/migrations/rest-to-graphql/2026"
      label: "REST to GraphQL Migration"
  related_to:
    - id: "software/system-design/e-commerce-platform/2026"
      label: "E-Commerce Platform Architecture"
    - id: "software/debugging/graphql-n-plus-1-dataloader/2026"
      label: "GraphQL N+1 and DataLoader Debugging"
    - id: "software/system-design/api-gateway/2026"
      label: "API Gateway Design Patterns"
  alternative_to:
    - id: "software/system-design/rest-api-architecture/2026"
      label: "REST API Architecture at Scale"
  often_confused_with:
    - id: "software/migrations/rest-to-graphql/2026"
      label: "REST to GraphQL Migration (migration, not architecture design)"

# === SOURCES (7 authoritative sources) ===
sources:
  - id: src1
    title: "Introduction to Apollo Federation"
    author: Apollo GraphQL
    url: https://www.apollographql.com/docs/graphos/schema-design/federated-schemas/federation
    type: official_docs
    published: 2025-06-15
    reliability: high
  - id: src2
    title: "Evolving the Federated GraphQL Platform at Netflix"
    author: Netflix Technology Blog / InfoQ
    url: https://www.infoq.com/articles/federated-GraphQL-platform-Netflix/
    type: technical_blog
    published: 2023-08-10
    reliability: high
  - id: src3
    title: "Unifying Our GraphQL Design Patterns and Best Practices with Tutorials"
    author: Shopify Engineering
    url: https://shopify.engineering/unifying-graphql-design-patterns-best-practices-tutorials
    type: technical_blog
    published: 2024-01-15
    reliability: high
  - id: src4
    title: "GraphQL Security Best Practices"
    author: GraphQL Foundation
    url: https://graphql.org/learn/security/
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src5
    title: "GraphQL Specification — September 2025 Edition"
    author: GraphQL Foundation
    url: https://spec.graphql.org/September2025/
    type: rfc_spec
    published: 2025-09-08
    reliability: authoritative
  - id: src6
    title: "GraphQL Schema Stitching vs Federation"
    author: Tyk API Gateway
    url: https://tyk.io/blog/graphql-schema-stitching-vs-federation/
    type: technical_blog
    published: 2024-11-20
    reliability: moderate_high
  - id: src7
    title: "9 Ways To Secure Your GraphQL API — Security Checklist"
    author: Apollo GraphQL Blog
    url: https://www.apollographql.com/blog/9-ways-to-secure-your-graphql-api-security-checklist
    type: technical_blog
    published: 2024-05-10
    reliability: high
---

# GraphQL API Architecture at Scale

## TL;DR

- **Bottom line**: At scale, use Apollo Federation v2 to compose a supergraph from domain-owned subgraphs behind a single gateway — this gives teams independent deployability while clients see one unified API.
- **Key tool/command**: `rover supergraph compose --config supergraph.yaml` to validate and compose federated subgraph schemas before deployment.
- **Watch out for**: N+1 queries in resolvers — without DataLoader batching, a single nested GraphQL query can trigger hundreds of database calls.
- **Works with**: Apollo Federation v2 + Apollo Router, Netflix DGS (Java/Kotlin), GraphQL Mesh, any language with a GraphQL server library (graphql-js, graphql-java, gqlgen, Strawberry).

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- Always implement query depth limiting and complexity analysis in production — unbounded GraphQL queries can cause exponential database load and denial-of-service
- Never expose GraphQL introspection in production environments — it reveals your entire schema to attackers and enables automated exploitation
- DataLoader (or equivalent batching) is mandatory for any resolver that fetches from a database or external service — without it, N+1 queries will destroy performance
- Persisted queries or an allowlist of approved operations must be enforced in production to prevent arbitrary query injection and abuse
- Federation gateway (Apollo Router / GraphQL Mesh) must be the only public entry point — never expose subgraph endpoints directly to clients

## Quick Reference

| Component | Role | Technology Options | Scaling Strategy |
|---|---|---|---|
| Gateway / Router | Schema composition, query planning, routing to subgraphs | Apollo Router (Rust), GraphQL Mesh, Cosmo Router | Horizontal — stateless, deploy behind LB; cache query plans |
| Subgraph Services | Domain-owned partial schema + resolvers | Apollo Server, Netflix DGS, gqlgen (Go), Strawberry (Python) | Horizontal — independent scaling per domain team |
| Schema Registry | Version control, composition validation, breaking change detection | Apollo GraphOS, Hive (open-source), Cosmo | Central — single registry, CI/CD integration |
| DataLoader / Batching | Batch + cache data fetches within a single request | graphql/dataloader (JS), Spring BatchLoader (DGS), dataloaden (Go) | Per-request instance — no cross-request caching |
| Query Complexity Analyzer | Reject queries exceeding cost threshold before execution | graphql-query-complexity, Apollo cost analysis plugin | Configured at gateway — cost limits per client tier |
| Persisted Query Store | Map query hashes to approved operations | Redis, CDN edge, Apollo APQ, Relay Compiler | Cache at edge — hash lookup is O(1) |
| Caching Layer | Response + entity caching to reduce resolver execution | CDN (Cloudflare, Fastly), Redis, Apollo cache hints | Cache-Control headers + entity-level cache invalidation |
| Observability | Distributed tracing across gateway + subgraphs | OpenTelemetry, Apollo Studio, Datadog, Jaeger | Trace context propagation via HTTP headers |
| Rate Limiter | Per-client query budget based on complexity cost | Apollo Router plugins, Cloudflare WAF, custom middleware | Token bucket per API key; cost-based budgets |
| Auth / AuthZ | Authentication at gateway, authorization at resolver level | JWT validation at gateway, directive-based @auth in subgraphs | Gateway validates tokens; subgraphs enforce field-level access |

## Decision Tree

```
START
├── Single team, <5 entity types, <1K QPS?
│   ├── YES → Monolith GraphQL server (Apollo Server / graphql-yoga / gqlgen)
│   └── NO ↓
├── 2-5 teams, shared schema ownership?
│   ├── YES → Schema stitching with GraphQL Mesh or modular monolith with schema modules
│   └── NO ↓
├── 6+ teams, each owns a domain (users, products, orders)?
│   ├── YES → Apollo Federation v2 with subgraph-per-team
│   └── NO ↓
├── Java/Kotlin ecosystem, Spring Boot stack?
│   ├── YES → Netflix DGS Framework with Federation support
│   └── NO ↓
├── >50K QPS, need edge caching and query plan optimization?
│   ├── YES → Apollo Router (Rust) + persisted queries + CDN caching + entity cache
│   └── NO ↓
├── Need to combine GraphQL + REST + gRPC backends?
│   ├── YES → GraphQL Mesh as a unifying gateway layer
│   └── NO ↓
└── DEFAULT → Start with monolith GraphQL server, extract to federation when team count exceeds 3
```

## Step-by-Step Guide

### 1. Define your supergraph schema with domain boundaries

Map your domain into bounded contexts. Each domain team owns a subgraph with its core types. Use the `@key` directive to declare entity identity, allowing other subgraphs to extend types across boundaries. Plan entity references before writing resolvers. [src1]

```graphql
# products subgraph — owns Product type
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Float!
  category: Category!
}

type Category {
  id: ID!
  name: String!
}

type Query {
  product(id: ID!): Product
  products(first: Int = 10, after: String): ProductConnection!
}
```

**Verify**: `rover subgraph check <graph>@<variant> --schema products.graphql --name products` → composition succeeds with no errors.

### 2. Implement entity references across subgraphs

When one subgraph needs data owned by another, use stub types with `@key` to declare a reference. The gateway resolves the full entity by calling the owning subgraph's `__resolveReference` function. [src1]

```graphql
# reviews subgraph — references Product from products subgraph
type Product @key(fields: "id") {
  id: ID!
  reviews: [Review!]!
  averageRating: Float
}

type Review {
  id: ID!
  author: User!
  body: String!
  rating: Int!
  createdAt: DateTime!
}
```

```javascript
// reviews subgraph — resolve Product references
const resolvers = {
  Product: {
    __resolveReference(product) {
      // product.id comes from the gateway's query plan
      return { id: product.id };  // return stub — reviews resolver fills the rest
    },
    reviews(product) {
      return reviewsLoader.load(product.id);  // DataLoader batching
    },
    averageRating(product) {
      return ratingsLoader.load(product.id);
    },
  },
};
```

**Verify**: Query through gateway: `{ product(id: "1") { name reviews { body rating } } }` → returns product name from products subgraph and reviews from reviews subgraph in a single response.

### 3. Set up the federation gateway with Apollo Router

Deploy Apollo Router as the single client-facing endpoint. It composes subgraph schemas into a supergraph, builds query plans, and routes operations to the appropriate subgraphs. [src1]

```yaml
# supergraph.yaml — Apollo Router configuration
supergraph:
  listen: 0.0.0.0:4000
  introspection: false  # disabled in production
subgraphs:
  products:
    routing_url: http://products-service:4001/graphql
    schema:
      subgraph_url: http://products-service:4001/graphql
  reviews:
    routing_url: http://reviews-service:4002/graphql
    schema:
      subgraph_url: http://reviews-service:4002/graphql
  users:
    routing_url: http://users-service:4003/graphql
    schema:
      subgraph_url: http://users-service:4003/graphql
```

```bash
# Compose and validate the supergraph
rover supergraph compose --config supergraph.yaml > supergraph.graphql

# Start the router
./router --supergraph supergraph.graphql --config router.yaml
```

**Verify**: `curl -X POST http://localhost:4000/ -H "Content-Type: application/json" -d '{"query":"{ __typename }"}'` → returns `{"data":{"__typename":"Query"}}`.

### 4. Implement DataLoader for N+1 prevention

Create request-scoped DataLoader instances for every data source a resolver calls. DataLoader batches all `.load(key)` calls within a single tick into one batch function call, turning N individual queries into 1 batched query. [src2]

```javascript
// dataloaders.js — Request-scoped DataLoader factory
const DataLoader = require("dataloader");  // dataloader@2.2
const db = require("./db");

function createLoaders() {
  return {
    productLoader: new DataLoader(async (ids) => {
      // Single query: SELECT * FROM products WHERE id IN (...)
      const products = await db.query(
        "SELECT * FROM products WHERE id = ANY($1)", [ids]
      );
      // Return in same order as input ids
      const map = new Map(products.map(p => [p.id, p]));
      return ids.map(id => map.get(id) || null);
    }),
    reviewsByProductLoader: new DataLoader(async (productIds) => {
      const reviews = await db.query(
        "SELECT * FROM reviews WHERE product_id = ANY($1)", [productIds]
      );
      // Group reviews by product_id, return arrays in input order
      const grouped = new Map();
      reviews.forEach(r => {
        if (!grouped.has(r.product_id)) grouped.set(r.product_id, []);
        grouped.get(r.product_id).push(r);
      });
      return productIds.map(id => grouped.get(id) || []);
    }),
  };
}

module.exports = { createLoaders };
```

**Verify**: Enable query logging on database → a query for 10 products with reviews produces exactly 2 SQL queries (1 for products, 1 for reviews), not 11.

### 5. Add query complexity analysis and depth limiting

Configure the gateway to reject queries that exceed a maximum depth or complexity cost before execution. Assign cost weights to fields based on their resolver expense. [src4]

```javascript
// Apollo Server with query complexity plugin
const { ApolloServer } = require("@apollo/server");  // @apollo/server@4.x
const { createComplexityLimitRule } = require("graphql-validation-complexity");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      // Cost per field (default 1), list multiplier
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10,  // lists multiply child cost by 10
      introspectionListFactor: 2,
      formatErrorMessage: (cost) =>
        `Query cost ${cost} exceeds maximum of 1000`,
    }),
  ],
  // Depth limiting
  plugins: [
    {
      async requestDidStart() {
        return {
          async didResolveOperation(ctx) {
            const depth = calculateDepth(ctx.document);
            if (depth > 10) {
              throw new Error(`Query depth ${depth} exceeds maximum of 10`);
            }
          },
        };
      },
    },
  ],
});
```

**Verify**: Send a deeply nested query (depth 15) → receive error response with `Query depth 15 exceeds maximum of 10`. Send a wide query with many list fields → receive `Query cost exceeds maximum of 1000`.

### 6. Implement persisted queries and client allowlisting

Use Automatic Persisted Queries (APQ) for public clients or a compiled operation allowlist for trusted clients. This prevents arbitrary query execution and reduces network payload. [src7]

```javascript
// Apollo Server with Automatic Persisted Queries
const { ApolloServer } = require("@apollo/server");
const { ApolloServerPluginCacheControl } = require("@apollo/server/plugin/cacheControl");
const { KeyvAdapter } = require("@apollo/utils.keyvadapter");
const Keyv = require("keyv");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  persistedQueries: {
    cache: new KeyvAdapter(new Keyv("redis://redis:6379")),
    ttl: 86400,  // 24 hours
  },
  plugins: [
    ApolloServerPluginCacheControl({ defaultMaxAge: 60 }),
  ],
});

// Client sends hash first (saves bandwidth):
// POST { "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "abc123..." } } }
// Server returns result if cached, or 'PersistedQueryNotFound' to trigger full send.
```

**Verify**: Send a query with only its SHA-256 hash → first request returns `PersistedQueryNotFound`, second request with full query registers it, third request with hash only returns cached result.

## Code Examples

### TypeScript: Apollo Federation Subgraph with Authentication

> Full script: [typescript-apollo-federation-subgraph-with-authent.ts](scripts/typescript-apollo-federation-subgraph-with-authent.ts) (29 lines)

```typescript
// products-subgraph/index.ts — Complete federated subgraph
// Input:  GraphQL queries routed from Apollo Router
// Output: Product data with field-level authorization
import { ApolloServer } from "@apollo/server";  // @apollo/server@4.11
import { buildSubgraphSchema } from "@apollo/subgraph";  // @apollo/subgraph@2.9
# ... (see full script)
```

### Go: gqlgen Subgraph with DataLoader

> Full script: [go-gqlgen-subgraph-with-dataloader.go](scripts/go-gqlgen-subgraph-with-dataloader.go) (31 lines)

```go
// resolvers/product.go — gqlgen resolver with dataloaden
// Input:  GraphQL product queries
// Output: Batched database responses
package resolvers
import (
# ... (see full script)
```

## Anti-Patterns

### Wrong: Exposing database structure as GraphQL schema

```graphql
// BAD — Schema mirrors database tables, not business domain
type products_table {
  product_id: Int!
  product_name: String
  category_fk: Int
  created_at: String
  updated_at: String
  is_deleted: Boolean
}

type Query {
  products_table(limit: Int, offset: Int): [products_table]
}
// Leaks implementation details, exposes internal IDs,
// forces clients to handle nulls and deleted rows.
```

### Correct: Design schema around business domain

```graphql
// GOOD — Schema represents business concepts, not tables [src3]
type Product @key(fields: "id") {
  id: ID!
  name: String!
  price: Money!
  category: Category!
  availability: Availability!
}

type Money {
  amount: Float!
  currency: CurrencyCode!
}

type Query {
  product(id: ID!): Product
  products(first: Int!, after: String): ProductConnection!
}
// Clean business types, Relay-style pagination,
// no leaked internals, strong typing.
```

### Wrong: Resolvers without DataLoader (N+1 problem)

```javascript
// BAD — Each product triggers a separate DB query for reviews
const resolvers = {
  Product: {
    reviews(product) {
      // Called N times for N products — N+1 total queries!
      return db.query("SELECT * FROM reviews WHERE product_id = $1", [product.id]);
    },
  },
};
// 100 products = 101 database queries (1 for products + 100 for reviews).
```

### Correct: Batched resolvers with DataLoader

```javascript
// GOOD — DataLoader batches all review fetches into 1 query [src2]
const resolvers = {
  Product: {
    reviews(product, _, { loaders }) {
      return loaders.reviewsByProductLoader.load(product.id);
      // 100 products = 2 database queries total
      // (1 for products + 1 batched for all reviews)
    },
  },
};
```

### Wrong: No query depth or complexity limits

```javascript
// BAD — Accepts any query, no matter how expensive
const server = new ApolloServer({ typeDefs, resolvers });
// Attacker sends: { users { friends { friends { friends { friends { name } } } } } }
// Exponential data explosion — server runs out of memory.
```

### Correct: Enforce depth + complexity limits at gateway

```javascript
// GOOD — Reject expensive queries before execution [src4]
const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(10), complexityLimit(1000)],
  introspection: false,  // disabled in production
});
// Deep/expensive queries rejected with clear error message.
// Introspection disabled so attackers can't enumerate schema.
```

### Wrong: Offset-based pagination

```graphql
# BAD — Offset pagination degrades at scale
type Query {
  products(limit: Int, offset: Int): [Product]
}
# offset: 100000 → database scans and skips 100K rows.
# Adding/removing items shifts pages, causing duplicates or missed items.
```

### Correct: Cursor-based (Relay-style) pagination

```graphql
# GOOD — Cursor pagination is stable and performant [src3]
type Query {
  products(first: Int!, after: String): ProductConnection!
}

type ProductConnection {
  edges: [ProductEdge!]!
  pageInfo: PageInfo!
}

type ProductEdge {
  cursor: String!
  node: Product!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}
# Cursor-based: O(1) seek regardless of page position.
# Stable pagination — inserts/deletes don't shift pages.
```

## Common Pitfalls

- **N+1 queries in nested resolvers**: The most common GraphQL performance issue. Every resolver that accesses a data source must use DataLoader or batching. Without it, `products { reviews { author } }` on 50 products produces 1 + 50 + 50 = 101 queries. Fix: Create per-request DataLoader instances for every data source. [src2]
- **Federation composition failures in CI/CD**: Subgraph schema changes that break composition are only caught when all subgraphs are composed together. Fix: Run `rover subgraph check` in CI for every PR; use a schema registry (Apollo GraphOS, Hive) to detect breaking changes before merge. [src1]
- **Over-fetching via `SELECT *` in resolvers**: Resolvers fetch all columns from the database even when the client only requests 2 fields. Fix: Inspect `info.fieldNodes` to determine requested fields and generate targeted SQL queries, or use a library like `graphql-fields` or `joinMonster`. [src3]
- **Missing error handling in `__resolveReference`**: If a subgraph's reference resolver throws, the entire federated query fails. Fix: Return `null` with a partial error for missing entities instead of throwing; use `@inaccessible` for types still in development. [src1]
- **Client-side caching issues with mutations**: After a mutation, the client cache holds stale data because Apollo Client normalizes by `__typename:id`. Fix: Return the mutated object in the mutation response so the cache updates automatically; use `refetchQueries` or `cache.evict()` for complex invalidation. [src7]
- **Unbounded list fields without pagination**: A field like `reviews: [Review!]!` with no limit can return millions of rows. Fix: Always use connection-style pagination (`first`/`after`) and enforce a maximum `first` value at the schema level (e.g., max 100). [src4]
- **Gateway timeout on slow subgraphs**: One slow subgraph blocks the entire federated query. Fix: Set per-subgraph timeouts in the router config, return partial data with errors for timed-out subgraphs, and implement circuit breakers. [src2]
- **Schema drift between subgraph and gateway**: Deploying a subgraph before updating the gateway supergraph causes runtime errors. Fix: Use a CI/CD pipeline that composes the supergraph, validates compatibility, and deploys gateway + subgraphs atomically. [src1]

## Diagnostic Commands

```bash
# Validate supergraph composition (catches breaking changes)
rover supergraph compose --config supergraph.yaml

# Check a subgraph against the deployed supergraph
rover subgraph check <graph>@<variant> --schema products.graphql --name products

# Introspect a running subgraph (development only)
rover subgraph introspect http://localhost:4001/graphql

# Test query execution through the gateway
curl -X POST http://localhost:4000/ \
  -H "Content-Type: application/json" \
  -d '{"query":"{ product(id: \"1\") { name price reviews { rating } } }"}'

# Monitor Apollo Router metrics (Prometheus)
curl http://localhost:9090/metrics | grep apollo_router

# Check DataLoader cache hit rate in Node.js
# Add to DataLoader: { cacheMap: new Map(), batchScheduleFn: (cb) => setTimeout(cb, 10) }
# Log: console.log("Cache size:", loader._cacheMap.size);

# Trace query execution plan in Apollo Router
APOLLO_ROUTER_LOG=apollo_router::query_planner=debug ./router --supergraph supergraph.graphql
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| GraphQL Spec Sep 2025 | Current | Schema Coordinates, OneOf inputs | First spec update since Oct 2021; adds `@oneOf` for input unions |
| Apollo Federation v2.5+ | Current | None since v2.0 | Requires `@link` directive; supports `@shareable`, `@override` |
| Apollo Federation v2.0 | Stable | `@key` syntax changed from v1 | Migrate from v1: add `@link` import, replace `@requires(fields:)` syntax |
| Apollo Federation v1 | Deprecated | — | Upgrade to v2 — v1 lacks `@shareable`, `@override`, progressive migration |
| Netflix DGS 9.x | Current (Spring Boot 3.x) | Requires Java 17+ | DGS 9 aligns with Spring GraphQL; codegen updated |
| Netflix DGS 7.x | Maintenance | — | Upgrade to 9.x for Spring Boot 3 compatibility |
| GraphQL Mesh v1.x | Current | — | Replaces 0.x with stable API; unified config format |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Multiple teams need to contribute to a unified API independently | Single team owns the entire API surface | Monolith GraphQL server (Apollo Server, graphql-yoga) |
| Clients need flexible, nested data fetching in a single request | Simple CRUD with flat resources and no nesting | REST API with OpenAPI spec |
| Mobile + web clients have very different data needs for the same screen | All clients need identical data shapes | REST with versioned endpoints (v1, v2) |
| Need to compose multiple backend services (REST, gRPC, databases) into one API | Single backend database with no microservices | Direct GraphQL-to-database (Hasura, PostGraphile) |
| Schema evolution without breaking clients is critical | Strict API versioning is acceptable and preferred | REST with content negotiation or versioned URLs |
| Need real-time subscriptions alongside request-response queries | Only request-response needed, no real-time | REST API or gRPC for service-to-service |

## Important Caveats

- Apollo Federation v2 is not backward-compatible with v1 schemas — the `@link` directive and import syntax are required. Gradual migration from v1 to v2 is supported but requires updating all subgraphs.
- Netflix runs 800+ DGS subgraphs in production with federation — but they built extensive internal tooling (schema governance, automated testing, custom instrumentation). Do not assume the open-source DGS framework alone gives you Netflix-scale capabilities. [src2]
- Shopify's GraphQL design tutorial recommends designing around business domain objects, not database tables — this is the single most impactful architecture decision and affects everything downstream. [src3]
- GraphQL over HTTP is not yet fully standardized (working draft as of 2025). Most implementations use POST with JSON body, but GET for persisted queries varies. Always test with your specific client libraries.
- Federation adds latency per hop: a query touching 3 subgraphs makes 3 network calls from the gateway. For latency-sensitive paths, denormalize data into a single subgraph or use entity caching in the router.

## Related Units

- [REST to GraphQL Migration](/software/migrations/rest-to-graphql/2026)
- [GraphQL N+1 and DataLoader Debugging](/software/debugging/graphql-n-plus-1-dataloader/2026)
- [E-Commerce Platform Architecture](/software/system-design/e-commerce-platform/2026)
- [API Gateway Design Patterns](/software/system-design/api-gateway/2026)
- [REST API Architecture at Scale](/software/system-design/rest-api-architecture/2026)
