Skip to main content

Customization and extensibility

Work in progress

The Evolve Framework is an ongoing, backwards-compatible refactoring. Not all services have been migrated yet, and APIs may still evolve. Everything described here is functional but should be considered pre-release.

The framework provides sensible defaults for every commerce domain. When your project needs different behavior, you can override individual resolvers, extend schemas, or add entirely new modules.

Use modules as-is

The simplest approach: compose framework modules without modification. This is the right choice when the default resolvers and schemas match your requirements.

const module = new GraphQLCompositeModule([
new CustomerGraphQLModule(),
new CartGraphQLModule(),
new CheckoutGraphQLModule(),
]);

Override individual resolvers

When you need to change how a specific operation works, subclass the framework module and override the resolver:

import { CartGraphQLModule } from "@evolve-framework/commercetools";
import { customCartResolver } from "./resolvers/cart.ts";

export class ProjectCartModule extends CartGraphQLModule {
getResolvers() {
const resolvers = super.getResolvers();
resolvers.Query.cart = customCartResolver;
return resolvers;
}
}

// Compose with your override instead of the default:
const module = new GraphQLCompositeModule([
new CheckoutGraphQLModule(),
new ProjectCartModule(),
]);

This replaces only the cart query resolver. All other cart resolvers (mutations, field resolvers) keep their default behavior.

Module configuration

Modules accept a config object through their constructor. This is used to pass service-level settings (such as API keys or endpoints) into resolvers via the request context:

new CartGraphQLModule({
config: {
paymentServiceEndpoints: config.PAYMENT_SERVICE_ENDPOINTS,
},
});

GraphQLCompositeModule aggregates all module configs. Call module.getConfig() and pass it into the context factory so resolvers can access it as context.config.

Add project-specific modules

For functionality that does not exist in the framework, create your own module by extending AbstractGraphQLModule:

import { AbstractGraphQLModule } from "@evolve-framework/core";
import { gql } from "graphql-tag";

export class LoyaltyModule extends AbstractGraphQLModule {
typedefs = gql`
type LoyaltyAccount {
points: Int!
tier: String!
}
extend type Customer {
loyalty: LoyaltyAccount
}
`;

resolvers = {
Customer: {
loyalty: loyaltyResolver,
},
};
}

Compose it alongside framework modules:

const module = new GraphQLCompositeModule([
new CustomerGraphQLModule(),
new LoyaltyModule(),
]);

GraphQLCompositeModule merges all type definitions and resolvers, so your extend type Customer works seamlessly with the framework's customer schema.

Client context patterns

Your context.ts determines how the service authenticates with its SAAS backend. Each implementation package provides client context loaders for its integration. The @evolve-framework/commercetools package provides two patterns:

Direct authentication (account service only): the service manages tokens itself.

import {
CommercetoolsClientLoader,
clientFactory,
} from "@evolve-framework/commercetools";

const loader = new CommercetoolsClientLoader(clientFactory);

Federated authentication (all other services): the service delegates authentication to the account service.

import {
RemoteClientContextLoader,
clientFactory,
} from "@evolve-framework/commercetools";

const loader = new RemoteClientContextLoader(
config.ACCOUNT_SERVICE_ENDPOINT,
clientFactory,
);

Other implementation packages will provide their own client context loaders following the same patterns.

Testing approach

The framework ships with its own test suite covering default behavior. Your project tests should focus on what you own:

  • Custom resolvers: test any resolver you override or add
  • Module composition: verify that your module combination produces the expected schema
  • REST endpoints: test service-specific API routes
  • Event handlers and jobs: test project-specific business logic

Test utilities

The @evolve-framework/commercetools/testing export provides:

  • createTestServer(): creates a full GraphQL server with all modules for integration tests
  • createTestExecutor(): creates an HTTP executor with proper store context headers
  • Mock clients: ctMock and ctMockClient for mocking commercetools API calls

Test factories

The @evolve-framework/commercetools/factories export provides 20+ factories (built on fishery) for generating realistic test data: carts, customers, products, categories, orders, payments, and more. Use these instead of writing mock data by hand.

info

If you override a framework resolver, you take ownership of testing that resolver. The framework tests only cover default behavior.