Skip to content

Instantly share code, notes, and snippets.

@dberardo-com
Created February 14, 2024 07:24
Show Gist options
  • Save dberardo-com/e8ecab26ab16c0753f9f441f820f3cb6 to your computer and use it in GitHub Desktop.
Save dberardo-com/e8ecab26ab16c0753f9f441f820f3cb6 to your computer and use it in GitHub Desktop.
graphql federation
@Module({
imports: [
GraphQLModule.forRootAsync<ApolloGatewayDriverConfig>({
driver: ApolloGatewayDriver,
useFactory: async () => ({
...
gateway: {
fallbackPollIntervalInMs:
process.env.NODE_ENV == 'production' ? 0 : 6000,
supergraphSdl: new CustomIntrospectAndCompose({
subgraphs: services,
// subgraphHealthCheck: process.env.NODE_ENV !== 'production',
// @ts-ignore
fallbackPollIntervalInMs:
process.env.NODE_ENV == 'production' ? 0 : 6000,
// https://stackoverflow.com/questions/67598329/how-to-hot-reload-federation-gateway-in-nestjs
pollIntervalInMs: GQL_SUBGRAPH_RETRY_PERIOD,
}),
},
}),
}),
],
controllers: [...],
providers: [...],
})
import { IntrospectAndCompose, RemoteGraphQLDataSource, ServiceDefinition } from '@apollo/gateway';
import { composeServices } from '@apollo/composition';
import { Service, loadServicesFromRemoteEndpoint } from '@apollo/gateway/dist/supergraphManagers/IntrospectAndCompose/loadServicesFromRemoteEndpoint';
import { Logger } from '@nestjs/common';
import fetch from 'node-fetch';
// https://github.com/apollographql/federation/issues/355
// @ts-ignore
export class CustomIntrospectAndCompose extends IntrospectAndCompose {
public async checkSubgraphs(subgraphs: Service[]) {
const results = await Promise.all(
subgraphs.map(async (subgraph) => {
const subgraphResult = await new Promise((res) =>
fetch(subgraph?.url, {
signal: AbortSignal.timeout(3000), // https://fvdm.com/code/node-js-simple-request-timeout-with-fetch
method: "POST",
body: JSON.stringify({
query: `query { __typename }`,
variables: {},
})
})
.then(() => res(true))
.catch(() => {
Logger.warn(`Not able to load subgraph for ${subgraph.name}`)
res(false)
})
);
return { ...subgraph, success: subgraphResult };
})
);
return results.filter((result) => result.success);
}
private async updateSupergraphSdl() {
// @ts-ignore
const activeServiceList = await this.checkSubgraphs(this.subgraphs!);
const result = await loadServicesFromRemoteEndpoint({
serviceList: activeServiceList,
getServiceIntrospectionHeaders: async (service) => {
// @ts-ignore
return typeof this.config.introspectionHeaders === 'function'
// @ts-ignore
? await this.config.introspectionHeaders(service)
// @ts-ignore
: this.config.introspectionHeaders;
},
// @ts-ignore
serviceSdlCache: this.serviceSdlCache,
});
if (!result.isNewSchema) {
return null;
}
const supergraphSdl = this.createSupergraphFromSubgraphList(result.serviceDefinitions!);
// @ts-ignore
await this.healthCheck?.(supergraphSdl);
return supergraphSdl;
}
private createSupergraphFromSubgraphList(subgraphs: ServiceDefinition[]) {
const compositionResult = composeServices(subgraphs);
if (compositionResult.errors) {
const { errors } = compositionResult;
throw Error(
"A valid schema couldn't be composed. The following composition errors were found:\n" +
errors.map((e) => '\t' + e.message).join('\n'),
);
} else {
const { supergraphSdl } = compositionResult;
return supergraphSdl;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment