How to add Apollo Federation in Node.js

Apollo Federation enables distributed GraphQL architecture where multiple services contribute to a unified schema, supporting microservices patterns and team autonomy. As the creator of CoreUI, a widely used open-source UI library, I’ve implemented federated GraphQL architectures in enterprise systems throughout my 12 years of backend development since 2014. The most scalable approach is creating subgraph services with entity resolution and a gateway that composes the federated schema. This method provides service independence, type safety across services, and seamless schema composition without manual schema stitching.

Create subgraph services with @apollo/subgraph, define federated entities with @key directive, setup gateway with @apollo/gateway.

npm install @apollo/server @apollo/subgraph @apollo/gateway graphql
// services/users/index.js
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')
const { buildSubgraphSchema } = require('@apollo/subgraph')
const gql = require('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!
    username: String!
    email: String!
    createdAt: String!
  }

  type Query {
    user(id: ID!): User
    users: [User!]!
  }

  type Mutation {
    createUser(username: String!, email: String!): User!
  }
`

const users = [
  { id: '1', username: 'john', email: '[email protected]', createdAt: '2024-01-01' },
  { id: '2', username: 'jane', email: '[email protected]', createdAt: '2024-01-02' }
]

const resolvers = {
  Query: {
    user: (_, { id }) => users.find(user => user.id === id),
    users: () => users
  },

  Mutation: {
    createUser: (_, { username, email }) => {
      const user = {
        id: String(users.length + 1),
        username,
        email,
        createdAt: new Date().toISOString()
      }
      users.push(user)
      return user
    }
  },

  User: {
    __resolveReference: (reference) => {
      return users.find(user => user.id === reference.id)
    }
  }
}

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

startStandaloneServer(server, {
  listen: { port: 4001 }
}).then(({ url }) => {
  console.log(`Users service ready at ${url}`)
})
// services/posts/index.js
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')
const { buildSubgraphSchema } = require('@apollo/subgraph')
const gql = require('graphql-tag')

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

  type Post @key(fields: "id") {
    id: ID!
    title: String!
    content: String!
    authorId: ID!
    author: User!
    createdAt: String!
  }

  type User @key(fields: "id") {
    id: ID! @external
    posts: [Post!]!
  }

  type Query {
    post(id: ID!): Post
    posts: [Post!]!
  }

  type Mutation {
    createPost(title: String!, content: String!, authorId: ID!): Post!
  }
`

const posts = [
  { id: '1', title: 'First Post', content: 'Hello World', authorId: '1', createdAt: '2024-01-03' },
  { id: '2', title: 'Second Post', content: 'GraphQL Federation', authorId: '2', createdAt: '2024-01-04' }
]

const resolvers = {
  Query: {
    post: (_, { id }) => posts.find(post => post.id === id),
    posts: () => posts
  },

  Mutation: {
    createPost: (_, { title, content, authorId }) => {
      const post = {
        id: String(posts.length + 1),
        title,
        content,
        authorId,
        createdAt: new Date().toISOString()
      }
      posts.push(post)
      return post
    }
  },

  Post: {
    __resolveReference: (reference) => {
      return posts.find(post => post.id === reference.id)
    },
    author: (post) => {
      return { __typename: 'User', id: post.authorId }
    }
  },

  User: {
    posts: (user) => {
      return posts.filter(post => post.authorId === user.id)
    }
  }
}

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

startStandaloneServer(server, {
  listen: { port: 4002 }
}).then(({ url }) => {
  console.log(`Posts service ready at ${url}`)
})
// gateway/index.js
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway')

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'users', url: 'http://localhost:4001' },
      { name: 'posts', url: 'http://localhost:4002' }
    ],
    pollIntervalInMs: 10000
  })
})

const server = new ApolloServer({
  gateway
})

startStandaloneServer(server, {
  listen: { port: 4000 }
}).then(({ url }) => {
  console.log(`Gateway ready at ${url}`)
})
// gateway/index-with-auth.js
const { ApolloServer } = require('@apollo/server')
const { startStandaloneServer } = require('@apollo/server/standalone')
const { ApolloGateway, IntrospectAndCompose, RemoteGraphQLDataSource } = require('@apollo/gateway')

class AuthenticatedDataSource extends RemoteGraphQLDataSource {
  willSendRequest({ request, context }) {
    if (context.token) {
      request.http.headers.set('authorization', `Bearer ${context.token}`)
    }

    request.http.headers.set('user-id', context.userId || '')
  }
}

const gateway = new ApolloGateway({
  supergraphSdl: new IntrospectAndCompose({
    subgraphs: [
      { name: 'users', url: 'http://localhost:4001' },
      { name: 'posts', url: 'http://localhost:4002' }
    ]
  }),
  buildService({ url }) {
    return new AuthenticatedDataSource({ url })
  }
})

const server = new ApolloServer({
  gateway
})

startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async ({ req }) => {
    const token = req.headers.authorization || ''
    const userId = req.headers['user-id'] || ''

    return {
      token,
      userId
    }
  }
}).then(({ url }) => {
  console.log(`Authenticated gateway ready at ${url}`)
})
# Example federated query
query GetUserWithPosts {
  user(id: "1") {
    id
    username
    email
    posts {
      id
      title
      content
      createdAt
    }
  }
}

query GetPost {
  post(id: "1") {
    id
    title
    content
    author {
      id
      username
      email
    }
  }
}

Here the buildSubgraphSchema creates federated GraphQL schema from type definitions and resolvers. The @key directive marks User and Post types as entities that can be referenced across services. The __resolveReference resolver enables entity resolution by returning entity data when referenced from other services. The @external directive indicates fields defined in other subgraphs. The ApolloGateway composes multiple subgraph schemas into unified supergraph. The IntrospectAndCompose automatically fetches and combines schemas from subgraph endpoints. The RemoteGraphQLDataSource enables request customization for forwarding authentication headers. The pollIntervalInMs configures automatic schema updates when subgraphs change.

Best Practice Note:

This is the Apollo Federation architecture we use in CoreUI enterprise applications for scalable microservices GraphQL APIs. Implement proper error handling and fallback strategies for when subgraph services are unavailable, use managed federation with Apollo Studio for schema validation and deployment safety in production, version your federated schemas carefully to avoid breaking changes across services, and monitor federation performance with distributed tracing to identify bottlenecks in cross-service queries.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team