Subgraphs
Implementing services are now known as subgraphs.
If you find an outdated use of "implementing service," please submit article feedback or a pull request using the links on the right.
This article describes how to create a subgraph (formerly known as an implementing service) for a federated data 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:
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 subgraph. The first step is to install the @apollo/federation
package in our project:
npm install @apollo/federation
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:
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');
const typeDefs = gql`
type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
username: String
}
`;
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:
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 federated schema
Finally, we use the buildFederatedSchema
function from the @apollo/federation
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: buildFederatedSchema([{ typeDefs, resolvers }])
});
server.listen(4001).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
The server is now ready to act as a subgraph in a federated data 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):
const { ApolloServer, gql } = require('apollo-server');
const { buildFederatedSchema } = require('@apollo/federation');
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: buildFederatedSchema([{ typeDefs, resolvers }])
});
server.listen(4001).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Defining custom directives
The method for defining custom directives differs slightly in Apollo Federation.
Without Apollo Federation, you provide your directive definitions to the constructor of ApolloServer
in the schemaDirectives
argument, like so:
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
:
const { ApolloServer, gql, SchemaDirectiveVisitor } = require('apollo-server');
const { buildFederatedSchema } = require ('@apollo/federation')
// 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 = buildFederatedSchema([{ 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.
Securing subgraphs
Because of the power and flexibility of Apollo Federation's _entities
field, 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.