GraphQL schema stitching and components
Reference implementation around the concept of a partial schema / component similar to that discussed here.
This example takes advantage of existing graphql stitching capabilities from Apollo, but creates a convention
for how these schemas can be composed through imports and bindings.
This is very similar to the excellent graphql-modules
project — but closer to our own internal paradigm already in use for over a year and a half.
The future
For now it is alpha, but may become an official project.
Repository structure
lib
- the graphql-component code.examples/property-component
- a component implementation.examples/reviews-component
- a component implementation.examples/listing-component
- a component implementation composing Property
and Reviews
.examples/server
- the "application".
Running
Can be run with node examples/server/index.js
or npm start
which will start with debug flags.
Debugging
Enable debug logging with DEBUG=graphql-component:*
Activating fixtures
To intercept resolvers with mock fixtures execute this app with GRAPHQL_DEBUG=1
enabled.
In this example it must be done since no actual resolvers is implemented (with the exception of listing).
This works much like Apollo's addMockFunctionsToSchema
but functions better for this use case
because it will continue to use resolver when a fixture isn't present and the fixtures preserve the
memoization.
Usage
new GraphQLComponent({
types,
rootTypes,
resolvers,
fixtures,
imports,
directives,
context,
useFixtures
});
This will create an instance object of a component containing the following functions:
schema
- getter that returns an executable schema.context
- context builder.bindings
- provides a map to graphql-binding's create by imports
.
Aggregation
Example to merge multiple components:
const { schema, context } = new GraphQLComponent({
imports: [
new Author(),
new Book(),
new BookExtension()
]
});
const server = new ApolloServer({
schema,
context
});
This isn't necessary if you don't intend to expose another component's schema directly. You can also simply include
another component's types in your type definitions.
For example:
class BookWithAuthorComponent extends GraphQLComponent {
constructor() {
const types = [`
extend type Book {
author: Author
}
`, ...Book.types, ...Author.types];
super({ types, resolvers, });
}
}
module.exports = BookWithAuthorComponent;
This doesn't require using imports
.
NOTE: This may not be safe always and can result in type conflicts.
Using bindings
Binding provide a way to delegate to another schema using graphql-binding:
const resolvers = {
Book: {
author(book, args, context, info) {
return this.importBindings.get(Author).query.author({ id: book.authorId }, info, { context });
}
}
};
By simply importing an Author
component instance, it becomes possible to execute the resolver author
as a graphql call to resolve that type.
Resolver memoization
Schemas in graphql components will support the @memoize
directive. This will allow resolvers to be memoized within the
scope of a particular request context to reduce the number of times a resolver must run for the same data.
Example:
type Query {
author(id: ID!, version: String) : Author @memoize
}
Adding to context
Example context argument:
const context = {
namespace: 'myNamespace',
factory: function (request) {
return 'my value';
}
};