Docs
Launch GraphOS Studio
You're viewing documentation for a previous version of this software. Switch to the latest stable version.

Subgraphs


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

Defining a subgraph

To be part of a , a must conform to the Apollo Federation specification, which exposes the 's capabilities to the gateway, as well as to tools like Apollo Studio.

Converting an existing monolithic graph into a single is a convenient first step in building a federated . To start, here's a non-federated Apollo Server setup:

index.js
const { ApolloServer, gql } = require('apollo-server');
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,
});
server.listen(4001).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});

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 . The first step is to install the @apollo/subgraph package in our project:

npm install @apollo/subgraph

Defining an entity

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

index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
username: String
}
`;

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

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

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

Generating a subgraph schema

Finally, we use the buildSubgraphSchema function from the @apollo/subgraph package to augment our definition with federation support. We provide the result of this function to the ApolloServer constructor:

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

The server is now ready to act as a 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 ):

index.js
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
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 })
});
server.listen(4001).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});

Securing your subgraphs

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

Graph router
Users
subgraph
Products
subgraph
Reviews
subgraph
Web app
iOS app

Make sure to implement any necessary firewall rules, access control lists, or other measures to ensure that individual s 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 definitions like @key, the most useful of these definitions for debugging are two s of the Query type: _service and _entities:

type Query {
# ...your field definitions...
# Added automatically
_service: _Service!
_entities(representations: [_Any!]!): [_Entity]!
}

Query._service

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

query GetSubgraphSchema {
_service {
sdl
}
}

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

  • Unlike , the sdl is not disabled by default in production environments (this is safe if you properly secure your subgraph).
  • Unlike , the sdl 's returned string includes federation-specific s like @key.

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

Query._entities

Learn about entities if you haven't yet.

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

Whenever one erences another '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 s in the entity's @key.

_entities(representations: [_Any!]!): [_Entity]!
  • The _Any type is a special 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 's .

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

Query
query ($representations: [_Any!]!) {
_entities(representations: $representations) {
... on User {
id
username
}
}
}
Variable
{
"representations": [
{
"__typename": "User",
"id": "5"
}
]
}

Using in tests and debugging

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

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

Custom directives in subgraphs

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

⚠️ Important considerations

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

  • Custom s are not included in your graph's composed . The composition process strips all s. Only a given subgraph is aware of its own directives.
  • Because s are specific to individual s, it's valid for different subgraphs to define the same with different logic. Composition does not detect or warn about such inconsistencies.
  • If multiple s can resolve a particular , each subgraph should almost always apply the exact same set of custom s (with the exact same accompanying logic) to that field. Otherwise, the behavior of that field might vary depending on which resolves it.

Directives in Apollo Server 3.x

Apollo Server 3 does not provide built-in support for custom s, 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 's custom s.

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

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

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

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

const server = new ApolloServer({
schema: subgraphSchema
// ...other options...
});

Directives in Apollo Server 2.x

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

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

const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { buildSubgraphSchema } = require ('@apollo/subgraph')
// typeDefs and resolvers defined here
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field: GraphQLField<any, any>) {
field.isDeprecated = true;
field.deprecationReason = this.args.reason;
}
}
const directives = {
deprecated: DeprecatedDirective
};
let schema = buildSubgraphSchema({ typeDefs, resolvers });
SchemaDirectiveVisitor.visitSchemaDirectives(schema, directives);
const server = new ApolloServer({
schema: schema
});

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

Previous
Part 3 - Working with subgraphs
Next
The gateway
Edit on GitHubEditForumsDiscord