DEV Community

Cover image for Scaling APIs: GraphQL Federation Architecture
Prajapati Paresh
Prajapati Paresh

Posted on Originally published at smarttechdevs.in

Scaling APIs: GraphQL Federation Architecture

The Monolithic Graph Bottleneck

When engineering teams first adopt GraphQL, it feels like a revelation. The frontend can request exactly the data it needs, in a single network call, perfectly typed. However, as the enterprise grows, the GraphQL server often becomes a massive, monolithic bottleneck. If you have a single Node.js or Laravel server resolving the entire Graph for an e-commerce platform, that single codebase must understand Users, Products, Reviews, Inventory, and Billing.

When 50 backend engineers are pushing code to this single GraphQL monolith, deployment queues stall, schema conflicts become a daily nightmare, and a bug in the "Reviews" resolver can take down the "Billing" API. The traditional microservice solution—splitting into multiple REST APIs—destroys the very reason you chose GraphQL in the first place, forcing the frontend to stitch data back together manually.

At Smart Tech Devs, we solve this by architecting GraphQL Federation. Pioneered by Apollo, Federation allows you to split your monolithic GraphQL schema across multiple independent microservices (Subgraphs), while presenting a single, unified GraphQL endpoint (the Supergraph) to the frontend client.

Understanding the Supergraph Architecture

In a Federated architecture, your infrastructure is split into two distinct layers:

  • The Subgraphs: Independent microservices (e.g., a Laravel API for Users, a Node API for Products). Each Subgraph maintains its own database and its own GraphQL schema. Crucially, they can extend types defined by other subgraphs.
  • The Gateway (Router): A high-performance proxy (like Apollo Router) that sits in front of the subgraphs. It takes the incoming client query, analyzes it, breaks it down into smaller sub-queries, executes them in parallel across the relevant subgraphs, stitches the JSON responses back together, and returns a single payload to the client.

Phase 1: Architecting the User Subgraph (Laravel)

Let's build a multi-language Federated system. Our "Users" service will be built in Laravel using the nuwave/lighthouse package, which has native Federation support.

We define the User schema. We use the @key directive to tell the Apollo Gateway that a User can be uniquely identified by their id. This is the foundation of cross-service entity resolution.


# Users Subgraph (Laravel) - graphql/schema.graphql

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

type Query {
    me: User @auth
    user(id: ID! @eq): User @find
}

Phase 2: Extending Entities in the Reviews Subgraph (Node.js)

Here is where Federation shines. We have a separate microservice for "Reviews" built in Node.js. A Review belongs to a User. However, the Reviews database doesn't store the User's name or email; it only stores the author_id.

Instead of the frontend making two queries, we extend the User type inside the Reviews subgraph. We tell the Gateway: "Hey, I know about the User entity, and I can provide their 'reviews' if you give me their ID."


# Reviews Subgraph (Node.js/Apollo) - schema.graphql

# We define the Review type owned by this service
type Review {
    id: ID!
    body: String!
    rating: Int!
    author: User!
}

# We EXTEND the User type defined by the Laravel service!
extend type User @key(fields: "id") {
    id: ID! @external
    reviews: [Review!]!
}

type Query {
    latestReviews: [Review!]!
}

Now, we must write the Entity Resolver in the Node.js service to fetch those reviews when requested.


// Reviews Subgraph (Node.js) - resolvers.js
const resolvers = {
    User: {
        // This resolves the 'reviews' field when the Gateway passes in a User object
        reviews(user) {
            // Fetch reviews from the local Reviews database where author_id === user.id
            return fetchReviewsByAuthorId(user.id);
        }
    }
};

Phase 3: The Apollo Gateway (The Orchestrator)

The frontend never talks to Laravel or Node directly. It talks to the Gateway. The Gateway pulls the schemas from both subgraphs and merges them into one Supergraph.


// Gateway (Node.js) - index.js
const { ApolloServer } = require('@apollo/server');
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');

const gateway = new ApolloGateway({
    supergraphSdl: new IntrospectAndCompose({
        subgraphs: [
            { name: 'users', url: 'http://laravel-users-api.internal/graphql' },
            { name: 'reviews', url: 'http://node-reviews-api.internal/graphql' },
        ],
    }),
});

const server = new ApolloServer({ gateway });
server.listen({ port: 4000 }).then(({ url }) => {
    console.log(`🚀 Gateway ready at ${url}`);
});

The Execution Plan (How it works under the hood)

If a React client executes this query against the Gateway:


query {
    user(id: "1") {
        name
        email
        reviews {
            rating
            body
        }
    }
}

The Gateway generates a Query Plan. It executes it in two steps:
1. It hits the Laravel Subgraph: "Fetch name and email for User 1."
2. It takes the ID from that response and simultaneously hits the Node.js Subgraph: "Fetch reviews for User 1."

It stitches the JSON together and returns it to the client in milliseconds.

The Engineering ROI

GraphQL Federation completely decouples your engineering organization. The billing team can write in Go, the user team in PHP, and the data science team in Python. Each team deploys their subgraph independently, manages their own database schema, and scales their own hardware. Yet, despite this massive backend fragmentation, the frontend engineering team retains the ultimate developer experience: a single, perfectly documented, strongly typed GraphQL endpoint to query the entire enterprise ecosystem.

Top comments (0)