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
Defining a subgraph
To be part of a supergraph, a subgraph must conform to the
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:
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';const typeDefs = gql`type Query {me: User}type User {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},};const server = new ApolloServer({typeDefs,resolvers,});// Note the top-level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';const typeDefs = gql`type Query {me: User}type User {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},};const server = new ApolloServer({typeDefs,resolvers,});// Note the top-level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
Above, we wrap our schema in the gql
tag from the graphql-tag
package, converting our schema into an AST (i.e., DocumentNode
). While Apollo Server can accept a string
(or DocumentNode
) for its typeDefs
, the buildSubgraphSchema
DocumentNode
.
This should look familiar if you've
Now, let's convert this to a subgraph!
1. Install and import @apollo/subgraph
@apollo/subgraph
The first step is to install the @apollo/subgraph
package in our server project:
npm install @apollo/subgraph
We also need to require the buildSubgraphSchema
function from this package in in the file where we initialize ApolloServer
(we'll use it later):
import { buildSubgraphSchema } from '@apollo/subgraph';
import { buildSubgraphSchema } from '@apollo/subgraph';
2. Opt in to Federation 2
For a subgraph to use extend schema
definition:
import gql from 'graphql-tag';const typeDefs = gql`# highlight-startextend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])# highlight-endtype Query {me: User}type User {id: ID!username: String}`;
import gql from 'graphql-tag';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User {id: ID!username: String}`;
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 @key
and @shareable
, you'll need to add those directives to the import
array 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
const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User@key(fields: "id") { # highlight-lineid: ID!username: String}`;
const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User@key(fields: "id") {id: ID!username: String}`;
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:
const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};
const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};
(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:
const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});// Note the top level await!const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});// Note the top level await!const { url } = await startStandaloneServer(server);console.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):
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';import { buildSubgraphSchema } from '@apollo/subgraph';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User @key(fields: "id") {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';import gql from 'graphql-tag';import { buildSubgraphSchema } from '@apollo/subgraph';const typeDefs = gql`extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])type Query {me: User}type User @key(fields: "id") {id: ID!username: String}`;const resolvers = {Query: {me() {return { id: '1', username: '@ava' };},},User: {__resolveReference(user, { fetchUserById }) {return fetchUserById(user.id);},},};const server = new ApolloServer({schema: buildSubgraphSchema({ typeDefs, resolvers }),});const { url } = await startStandaloneServer(server);console.log(`🚀 Server ready at ${url}`);