Implementing a Subgraph with Apollo Server
This article demonstrates how to create a subgraph for a federated supergraph using Node.js and Apollo Server.
To create a subgraph using a different language and/or framework, see the list of Federation-compatible subgraph implementations. Note that not all listed libraries support all Federation features.
Defining a subgraph
To be part of a supergraph, a subgraph must conform to the Apollo Federation subgraph specification, which exposes the subgraph's capabilities to your graph router, as well as to tools like Apollo Studio.
Converting an existing monolithic graph into a single subgraph is a convenient first step in building a federated supergraph. To start, here's a non-federated Apollo Server setup:
1import { ApolloServer } from '@apollo/server'; 2import { startStandaloneServer } from '@apollo/server/standalone'; 3import gql from 'graphql-tag'; 4 5const typeDefs = gql` 6 type Query { 7 me: User 8 } 9 10 type User { 11 id: ID! 12 username: String 13 } 14`; 15 16const resolvers = { 17 Query: { 18 me() { 19 return { id: '1', username: '@ava' }; 20 }, 21 }, 22}; 23 24const server = new ApolloServer({ 25 typeDefs, 26 resolvers, 27}); 28 29// Note the top-level await! 30const { url } = await startStandaloneServer(server); 31console.log(`🚀 Server ready at ${url}`);Above, we wrap our schema in the
gqltag from thegraphql-tagpackage, converting our schema into an AST (i.e.,DocumentNode). While Apollo Server can accept astring(orDocumentNode) for itstypeDefs, thebuildSubgraphSchemafunction below requires the schema we pass in to be aDocumentNode.
This should look familiar if you've set up Apollo Server before. If you haven't, we recommend you familiarize yourself with the basics before jumping into federation.
Now, let's convert this to a subgraph!
1. Install and import @apollo/subgraph
The first step is to install the @apollo/subgraph package in our server project:
1npm install @apollo/subgraphWe also need to require the buildSubgraphSchema function from this package in the file where we initialize ApolloServer (we'll use it later):
1import { buildSubgraphSchema } from '@apollo/subgraph';2. Opt in to Federation 2
For a subgraph to use new features in Federation 2, its schema needs to include the following extend schema definition:
1import gql from 'graphql-tag'; 2 3const typeDefs = gql` 4 extend schema 5 @link( 6 url: "https://specs.apollo.dev/federation/v2.0" 7 import: ["@key", "@shareable"] 8 ) 9 10 type Query { 11 me: User 12 } 13 14 type User { 15 id: ID! 16 username: String 17 } 18`;This definition enables the schema to use Federation 2 features. Without it, Federation 2 composition assumes that a subgraph is using Federation 1, which sets certain defaults for backward compatibility.
As you begin using more federation-specific directives beyond
@keyand@shareable, you'll need to add those directives to theimportarray shown above.
3. Define an entity
Entities aren't required in a subgraph, but they're a core building block of a federated supergraph, so it's good to get some practice defining them.
As part of our federated architecture, we want other subgraphs to be able to contribute fields to the User type. To enable this, we add the @key directive to the User type's definition to designate it as an entity:
1const typeDefs = gql` 2 extend schema 3 @link( 4 url: "https://specs.apollo.dev/federation/v2.0" 5 import: ["@key", "@shareable"] 6 ) 7 8 type Query { 9 me: User 10 } 11 12 type User 13 @key(fields: "id") { 14 id: ID! 15 username: String 16 } 17`;The @key directive tells the gateway which field(s) of the User entity can uniquely identify a particular instance of it. In this case, the gateway can use the single field id.
Next, we add a reference resolver for the User entity. A reference resolver tells the gateway how to fetch an entity by its @key fields:
1const resolvers = { 2 Query: { 3 me() { 4 return { id: '1', username: '@ava' }; 5 }, 6 }, 7 User: { 8 __resolveReference(user, { fetchUserById }) { 9 return fetchUserById(user.id); 10 }, 11 }, 12};(This example requires defining the fetchUserById function to obtain the appropriate User from our backing data store.)
4. Generate the subgraph schema
Finally, we use the buildSubgraphSchema function from the @apollo/subgraph package to augment our schema definition with federation support. We provide the result of this function to the ApolloServer constructor:
1const server = new ApolloServer({ 2 schema: buildSubgraphSchema({ typeDefs, resolvers }), 3}); 4 5// Note the top level await! 6const { url } = await startStandaloneServer(server); 7console.log(`🚀 Server ready at ${url}`);The server is now ready to act as a subgraph in a federated graph!
Combined example
Here are the snippets above combined (again, note that for this sample to be complete, you must define the fetchUserById function for your data source):
1import { ApolloServer } from '@apollo/server'; 2import { startStandaloneServer } from '@apollo/server/standalone'; 3import gql from 'graphql-tag'; 4import { buildSubgraphSchema } from '@apollo/subgraph'; 5 6const typeDefs = gql` 7 extend schema 8 @link( 9 url: "https://specs.apollo.dev/federation/v2.0" 10 import: ["@key", "@shareable"] 11 ) 12 13 type Query { 14 me: User 15 } 16 17 type User @key(fields: "id") { 18 id: ID! 19 username: String 20 } 21`; 22 23const resolvers = { 24 Query: { 25 me() { 26 return { id: '1', username: '@ava' }; 27 }, 28 }, 29 User: { 30 __resolveReference(user, { fetchUserById }) { 31 return fetchUserById(user.id); 32 }, 33 }, 34}; 35 36const server = new ApolloServer({ 37 schema: buildSubgraphSchema({ typeDefs, resolvers }), 38}); 39 40const { url } = await startStandaloneServer(server); 41console.log(`🚀 Server ready at ${url}`);