A library for building modular and composable GraphQL schemas through a component-based architecture.
graphql-component
enables you to build GraphQL schemas progressively through a tree of components. Each component encapsulates its own schema, resolvers, and data sources, making it easier to build and maintain large GraphQL APIs.
Read more about the architecture principles in our blog post.
- 🔧 Modular Schema Design: Build schemas through composable components
- 🔄 Schema Stitching: Merge multiple component schemas seamlessly
- 🚀 Apollo Federation Support: Build federated subgraphs with component architecture
- 📦 Data Source Management: Simplified data source injection and overrides
- 🛠️ Flexible Configuration: Extensive options for schema customization
npm install graphql-component
const GraphQLComponent = require('graphql-component');
const { schema, context } = new GraphQLComponent({
types,
resolvers
});
A GraphQLComponent
instance creates a GraphQL schema in one of two ways:
- With Imports: Creates a gateway/aggregate schema by combining imported component schemas with local types/resolvers
-
Without Imports: Uses
makeExecutableSchema()
to generate a schema from local types/resolvers
To create Apollo Federation subgraphs, set federation: true
in the component options:
const component = new GraphQLComponent({
types,
resolvers,
federation: true
});
This uses @apollo/federation
's buildSubgraphSchema()
instead of makeExecutableSchema()
.
new GraphQLComponent(options: IGraphQLComponentOptions)
-
types
:string | string[]
- GraphQL SDL type definitions -
resolvers
:object
- Resolver map for the schema -
imports
:Array<Component | ConfigObject>
- Components to import -
context
:{ namespace: string, factory: Function }
- Context configuration -
mocks
:boolean | object
- Enable default or custom mocks -
dataSources
:Array<DataSource>
- Data source instances -
dataSourceOverrides
:Array<DataSource>
- Override default data sources -
federation
:boolean
- Enable Apollo Federation support (default:false
) -
pruneSchema
:boolean
- Enable schema pruning (default:false
) -
pruneSchemaOptions
:object
- Schema pruning options -
transforms
:Array<Transform>
- Schema transformation functions
interface IGraphQLComponent {
readonly name: string;
readonly schema: GraphQLSchema;
readonly context: IContextWrapper;
readonly types: TypeSource;
readonly resolvers: IResolvers<any, any>;
readonly imports?: (IGraphQLComponent | IGraphQLComponentConfigObject)[];
readonly dataSources?: IDataSource[];
readonly dataSourceOverrides?: IDataSource[];
federation?: boolean;
}
class PropertyComponent extends GraphQLComponent {
constructor(options) {
super({
types,
resolvers,
...options
});
}
}
const { schema, context } = new GraphQLComponent({
imports: [
new PropertyComponent(),
new ReviewsComponent()
]
});
const server = new ApolloServer({ schema, context });
Data sources in graphql-component
use a proxy-based approach for context injection. The library provides two key types to assist with correct implementation:
// When implementing a data source:
class MyDataSource implements DataSourceDefinition<MyDataSource> {
name = 'MyDataSource';
// Context must be the first parameter when implementing
async getUserById(context: ComponentContext, id: string) {
// Use context for auth, config, etc.
return { id, name: 'User Name' };
}
}
// In resolvers, context is automatically injected:
const resolvers = {
Query: {
user(_, { id }, context) {
// Don't need to pass context - it's injected automatically
return context.dataSources.MyDataSource.getUserById(id);
}
}
}
// Add to component:
new GraphQLComponent({
types,
resolvers,
dataSources: [new MyDataSource()]
});
-
DataSourceDefinition<T>
: Interface for implementing data sources - methods must accept context as first parameter -
DataSource<T>
: Type representing data sources after proxy wrapping - context is automatically injected
This type system ensures proper context handling while providing a clean API for resolver usage.
import {
GraphQLComponent,
DataSourceDefinition,
ComponentContext
} from 'graphql-component';
// Define your data source with proper types
class UsersDataSource implements DataSourceDefinition<UsersDataSource> {
name = 'users';
// Static property
defaultRole = 'user';
// Context is required as first parameter when implementing
async getUserById(context: ComponentContext, id: string): Promise<User> {
// Access context properties (auth, etc.)
const apiKey = context.config?.apiKey;
// Implementation details...
return { id, name: 'User Name', role: this.defaultRole };
}
async getUsersByRole(context: ComponentContext, role: string): Promise<User[]> {
// Implementation details...
return [
{ id: '1', name: 'User 1', role },
{ id: '2', name: 'User 2', role }
];
}
}
// In resolvers, the context is automatically injected
const resolvers = {
Query: {
user: (_, { id }, context) => {
// No need to pass context - it's injected by the proxy
return context.dataSources.users.getUserById(id);
},
usersByRole: (_, { role }, context) => {
// No need to pass context - it's injected by the proxy
return context.dataSources.users.getUsersByRole(role);
}
}
};
// Component configuration
const usersComponent = new GraphQLComponent({
types: `
type User {
id: ID!
name: String!
role: String!
}
type Query {
user(id: ID!): User
usersByRole(role: String!): [User]
}
`,
resolvers,
dataSources: [new UsersDataSource()]
});
You can override data sources when needed (for testing or extending functionality). The override must follow the same interface:
// For testing - create a mock data source
class MockUsersDataSource implements DataSourceDefinition<UsersDataSource> {
name = 'users';
defaultRole = 'admin';
async getUserById(context: ComponentContext, id: string) {
return { id, name: 'Mock User', role: this.defaultRole };
}
async getUsersByRole(context: ComponentContext, role: string) {
return [{ id: 'mock', name: 'Mock User', role }];
}
}
// Use the component with overrides
const testComponent = new GraphQLComponent({
imports: [usersComponent],
dataSourceOverrides: [new MockUsersDataSource()]
});
// In tests
const context = await testComponent.context({});
const mockUser = await context.dataSources.users.getUserById('any-id');
// mockUser will be { id: 'any-id', name: 'Mock User', role: 'admin' }
The repository includes example implementations:
npm run start-composition
npm run start-federation
Both examples are accessible at http://localhost:4000/graphql
Enable debug logging with:
DEBUG=graphql-component:* node your-app.js
-
src/
- Core library code -
examples/
-
composition/
- Schema composition example -
federation/
- Federation implementation example
-
Please read our contributing guidelines (link) for details on our code of conduct and development process.
This project is licensed under the MIT License - see the LICENSE file for details.