Subgraphs


This article describes how to create a subgraph for a federated graph using Apollo Server.

Defining a subgraph

To be part of a supergraph, a subgraph must conform to the Apollo Federation specification, which exposes the subgraph's capabilities to the gateway, 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:

JavaScript
index.js
1const { ApolloServer, gql } = require('apollo-server');
2
3const typeDefs = gql`
4  type Query {
5    me: User
6  }
7
8  type User {
9    id: ID!
10    username: String
11  }
12`;
13
14const resolvers = {
15  Query: {
16    me() {
17      return { id: "1", username: "@ava" }
18    }
19  }
20};
21
22const server = new ApolloServer({
23  typeDefs,
24  resolvers,
25});
26
27server.listen(4001).then(({ url }) => {
28    console.log(`🚀 Server ready at ${url}`);
29});

This should look familiar if you've set up Apollo Server before. If it doesn't, we recommend you familiarize yourself with the basics before jumping into federation.

Now, let's convert this to a subgraph. The first step is to install the @apollo/subgraph package in our project:

shell
1npm install @apollo/subgraph

Defining an entity

As part of our federated architecture, we want other subgraphs to be able to extend the User type this subgraph defines. To enable this, we add the @key directive to the User type's definition to designate it as an entity:

JavaScript
index.js
1const { ApolloServer, gql } = require('apollo-server');
2const { buildSubgraphSchema } = require('@apollo/subgraph');
3
4const typeDefs = gql`
5  type Query {
6    me: User
7  }
8
9  type User @key(fields: "id") {
10    id: ID!
11    username: String
12  }
13`;

The @key directive tells other subgraphs which field(s) of the User type to use to uniquely identify a particular instance. In this case, subgraphs should use the single field id.

Next, we add a reference resolver for the User type. A reference resolver tells the gateway how to fetch an entity by its @key fields:

JavaScript
index.js
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.)

Learn more about entities

Generating a 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:

JavaScript
index.js
1const server = new ApolloServer({
2  schema: buildSubgraphSchema({ typeDefs, resolvers })
3});
4
5server.listen(4001).then(({ url }) => {
6    console.log(`🚀 Server ready at ${url}`);
7});

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):

JavaScript
index.js
1const { ApolloServer, gql } = require('apollo-server');
2const { buildSubgraphSchema } = require('@apollo/subgraph');
3
4const typeDefs = gql`
5  type Query {
6    me: User
7  }
8
9  type User @key(fields: "id") {
10    id: ID!
11    username: String
12  }
13`;
14
15const resolvers = {
16  Query: {
17    me() {
18      return { id: "1", username: "@ava" }
19    }
20  },
21  User: {
22    __resolveReference(user, { fetchUserById }){
23      return fetchUserById(user.id)
24    }
25  }
26}
27
28const server = new ApolloServer({
29  schema: buildSubgraphSchema({ typeDefs, resolvers })
30});
31
32server.listen(4001).then(({ url }) => {
33    console.log(`🚀 Server ready at ${url}`);
34});

Securing your subgraphs

Because of the power and flexibility of the Query._entities and Query._service fields, your subgraphs should not be directly accessible by clients. Instead, only your gateway should have access to your subgraphs. Clients then communicate with the gateway:

Make sure to implement any necessary firewall rules, access control lists, or other measures to ensure that individual subgraphs can be accessed only via the gateway.

Subgraph-specific symbols

When you generate your subgraph schema, some federation-specific definitions are automatically added to it. In addition to directive definitions like @key, the most useful of these definitions for debugging are two fields of the Query type: _service and _entities:

GraphQL
1type Query {
2  # ...your field definitions...
3
4  # Added automatically
5  _service: _Service!
6  _entities(representations: [_Any!]!): [_Entity]!
7}

Query._service

This field returns a _Service object with one field of its own: sdl. You can query it like so:

GraphQL
1query GetSubgraphSchema {
2  _service {
3    sdl
4  }
5}

The sdl field returns your subgraph's schema as an SDL string. This field has a couple of important differences from a standard introspection query that a tool like Apollo Sandbox uses:

  • Unlike introspection, the sdl field is not disabled by default in production environments (this is safe if you properly secure your subgraph).

  • Unlike introspection, the sdl field's returned string includes federation-specific directives like @key.

Whenever your gateway needs to fetch a subgraph's schema (this occurs only if your gateway uses IntrospectAndCompose), it uses this field instead of an introspection query so it can obtain federation-specific details.

Query._entities

Learn about entities if you haven't yet.

This field takes a list of entity representations and returns a list of corresponding entities.

Whenever one subgraph references another subgraph's entity, it uses an entity representation to do so. An entity representation is an object that includes only the entity's __typename and the fields in the entity's @key.

GraphQL
1_entities(representations: [_Any!]!): [_Entity]!
  • The _Any type is a special scalar that enables you to provide entity representations of any valid shape.

  • The _Entity type is a generated union type that includes every entity defined in your subgraph's schema.

You can query this field like so, providing a value for the $representations variable as shown:

GraphQL
Query
1query ($representations: [_Any!]!) {
2  _entities(representations: $representations) {
3    ... on User {
4      id
5      username
6    }
7  }
8}
JSON
Variable
1{
2  "representations": [
3    {
4      "__typename": "User",
5      "id": "5"
6    }
7  ]
8}

Using in tests and debugging

If you're writing integration tests for your subgraph, you can test the return value of the _entities field for various entity representations that your other subgraphs use.

If you're developing your subgraph in your local environment, you can mock the return value of the _entities field for your other subgraphs so you don't have to connect those subgraphs to their respective data stores.

Custom directives in subgraphs

The method for defining custom directives differs slightly for a federated graph, and it also depends on the version of Apollo Server you're using.

⚠️ Important considerations

Before you use directives in a federated graph, make sure to consider the following:

  • Custom directives are not included in your graph's composed supergraph schema. The composition process strips all subgraph directives. Only a given subgraph is aware of its own directives.

  • Because directives are specific to individual subgraphs, it's valid for different subgraphs to define the same directive with different logic. Composition does not detect or warn about such inconsistencies.

  • If multiple subgraphs can resolve a particular field, each subgraph should almost always apply the exact same set of custom directives (with the exact same accompanying logic) to that field. Otherwise, the behavior of that field might vary depending on which subgraph resolves it.

Directives in Apollo Server 3.x

Apollo Server 3 does not provide built-in support for custom directives, but you can install certain @graphql-tools libraries to enable support. To get started with these libraries in Apollo Server, first read Creating schema directives.

As the linked article describes, in Apollo Server 3 you define a transformer function for each of your subgraph schema's custom directives.

To apply transformer functions to your executable subgraph schema, you first generate the subgraph schema with buildSubgraphSchema as usual:

JavaScript
1let subgraphSchema = buildSubgraphSchema({typeDefs, resolvers});

But instead of passing the result directly to the ApolloServer constructor, you first apply all of your transformer functions to it:

JavaScript
1// Transformer function for an @upper directive
2subgraphSchema = upperDirectiveTransformer(subgraphSchema, 'upper');

After applying all transformer functions, you provide your final subgraph schema to the ApolloServer constructor as usual:

JavaScript
1const server = new ApolloServer({
2  schema: subgraphSchema
3  // ...other options...
4});

Directives in Apollo Server 2.x

Without Apollo Federation, you provide your directive definitions to the constructor of ApolloServer in the schemaDirectives argument, like so:

Expand example
JavaScript
1const { ApolloServer, gql, SchemaDirectiveVisitor } = require("apollo-server");
2
3// typeDefs and resolvers defined here
4
5class DeprecatedDirective extends SchemaDirectiveVisitor {
6  public visitFieldDefinition(field: GraphQLField<any, any>) {
7    field.isDeprecated = true;
8    field.deprecationReason = this.args.reason;
9  }
10}
11
12const server = new ApolloServer({
13  typeDefs,
14  resolvers,
15  schemaDirectives: {
16    deprecated: DeprecatedDirective
17  }
18});

With Apollo Federation, you instead call SchemaDirectiveVisitor.visitSchemaDirectives, passing in your schema and your directives, before you provide your schema to the constructor of ApolloServer:

JavaScript
1const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
2const { buildSubgraphSchema } = require ('@apollo/subgraph')
3
4// typeDefs and resolvers defined here
5
6class DeprecatedDirective extends SchemaDirectiveVisitor {
7  public visitFieldDefinition(field: GraphQLField<any, any>) {
8    field.isDeprecated = true;
9    field.deprecationReason = this.args.reason;
10  }
11}
12
13const directives = {
14  deprecated: DeprecatedDirective
15};
16let schema = buildSubgraphSchema({ typeDefs, resolvers });
17
18SchemaDirectiveVisitor.visitSchemaDirectives(schema, directives);
19
20const server = new ApolloServer({
21  schema: schema
22});

Also make sure to read about the gateway's support for custom directives.

Edit on GitHub

Forums