Problem
In a federated nest app, a gateway collects all the schemas from other services and form a complete graph. The question is, how to re-run the schema collection after a sub-schema has been changed?
Current Workaround
Restarting the gateway solves the problem, but it does not seem like an elegant solution.
Other Resources
Apollo server supports managed federation which essentially reverts the dependency between the gateway and the services. Sadly I couldn't find anything relating it to NestJS.
When configuring gateway application with NestJS, and when already have integrated with Apollo studio, then you need not define any serviceList in GraphQLGatewayModule. This is how your module initialization should look like:
GraphQLGatewayModule.forRootAsync({
useFactory: async () => ({
gateway: {},
server: {
path: '/graphql',
},
}),
})
Following environment variables should be declared on the machine hosting your gateway application:
APOLLO_KEY: "service:<graphid>:<hash>"
APOLLO_SCHEMA_CONFIG_DELIVERY_ENDPOINT: "https://uplink.api.apollographql.com/"
Post deployment of Federated GraphQL service, you may need to run apollo/rover CLI service:push command like below to update the schema which writes to schema registry and then gets pushed to uplink URL which is polled by gateway periodically:
npx apollo service:push --graph=<graph id> --key=service:<graph id>:<hash> --variant=<environment name> --serviceName=<service name> --serviceURL=<URL of your service with /graphql path> --endpoint=<URL of your service with /graphql path>
You can add a pollIntervalInMs option to the supergraphSdl configuration.
That will automatically poll the services again in each interval.
#Module({
imports: [
GraphQLModule.forRootAsync<ApolloGatewayDriverConfig>({
driver: ApolloGatewayDriver,
useFactory: async () => ({
server: {
path: '/graphql',
cors: true
},
gateway: {
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'example-service', url: 'http://localhost:8081/graphql' },
],
pollIntervalInMs: 15000,
})
},
})
})
],
})
export class AppModule {}
Related
In an NX monorepo I'm building 3 nestjs application, an auth-service + user-service and a gateway to start off with. They're all powered by apollo graphql and following the official nestjs documentation.
The issue that I'm having is that with both the user-service and auth-service up and processing requests successfully as individual servers, at the same time, the gateway throws
Couldn't load service definitions for "auth" at http://localhost:3100/apis/auth-service/graphql: 400: Bad Request
The services themselves are standard graphql applications, nothing basic.
The definitions for the gateway are as such:
{
server: {
debug: true,
playground: true,
autoSchemaFile: './apps/gateway/schema.gql',
sortSchema: true,
introspection: true,
cors: ['*'],
path: '/apis/gateway/graphql';
},
gateway: {
supergraphSdl: new IntrospectAndCompose({
subgraphHealthCheck: true,
subgraphs: [
{
name: 'user',
url: resolveSubgraphUrl('user'),
},
{
name: 'auth',
url: resolveSubgraphUrl('auth'),
},
],
}),
}
}
I have created an nx based monorepository that allows you to reproduce this issue easily, by spinning up all 3 servers in watch mode at the same time, on localhost, on different ports.
Everything is mapped as it should. Link: https://github.com/sebastiangug/nestjs-federation.git
The README contains the two commands necessary to run it as well as the same set of instructions plus the health-checks queries available from each service.
Versions used:
"#apollo/subgraph": "2.1.3",
"#apollo/federation": "0.37.1",
"#apollo/gateway": "2.1.3",
"apollo-server-express": "3.6.7",
"graphql": "16.5.0",
"#nestjs/graphql": "10.1.3",
"#nestjs/platform-express": "9.0.8",
Any ideas what I'm doing wrong here or what further configuration is necessary to achieve this?
Thank you
In app.module.ts I have the following:
#Module({
imports: [
...,
GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
server: {
context: getContext,
},
driver: ApolloGatewayDriver,
gateway: {
buildService: ({ name, url }) => {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }: any) {
...
},
});
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'iam', url: API_URL_IAM },
],
})
},
}),
]
...
})
here getContext is just a regular function which is not part of nestjs context (doesn't have injection, module capability) like below:
export const getContext = async ({ req }) => {
return {}
}
Is there any way to use nestjs services instead of plain old functional approach to build the context for graphql gateway in nestjs?
Thanks in advance for any kind of help.
I believe you're looking to create a service that is #Injectable and you can use that injectable service via a provider. What a provider will do is satisfy any dependency injection necessary.
In your scenario, I would import other modules as necessary. For building context, I would create a config file to create from env variables. Then create a custom provider that reads from the env variables and provides that implementation of the class/service to the other classes as their dependency injection.
For example, if I have a graphQL module. I would import the independent module. Then, I would provide in the providers section, the handler/service classes and the dependencies as an #injectable. Once your service class is created based on your config (which your provider class would handle), you would attach that service class to your GraphQL class to maybe lets say direct the URL to your dev/prod envs.
We use apollo-server-express to expose a graphql server.
For this server, we have set the introspection variable to false to hide our schema from the outer world which works fine for Graphql calls that go over rest calls.
However, when we set up a websocket connection with this same server, we manage to execute introspection queries, even though that during the instantiation of the apollo server, the introspection is explicitly set to false
the config for booting the Apollo-server looks something like this:
{
schema: <schema>,
context: <context_function>,
formatError: <format_error_function>,
debug: false,
tracing: false,
subscriptions: {
path: <graphQl_path>,
keepAlive: <keep_alive_param>,
onConnect: <connect_function>,
onDisconnect: <disconnect_function>
},
introspection: false,
playground: false
};
Did someone had a similar issue? And if yes, were you able to solve it and how?
apollo-server-express version = 2.1.0
npm version = 6.4.1
node version = 10.13.0
What ApolloServer does internally is prevent you from using the __schema and __type resolvers. I assume you could do the same thing:
export const resolvers = {
Query: {
__type() {
throw new Error('You cannot make introspection');
},
__schema() {
throw new Error('You cannot make introspection');
}
}
}
In Apollo Federation, I am facing this problem:
The gateway needs to be restarted every time we make a change in the schema of any federated service in service list.
I understand that every time a gateway starts and it collects all the schema and aggregates the data graph. But is there a way this can be handled automatically without restarting the Gateway as it will down all other unaffected GraphQL Federated services also
Apollo GraphQL , #apollo/gateway
There is an experimental poll interval you can use:
const gateway = new ApolloGateway({
serviceList: [
{ name: "products", url: "http://localhost:4002" },
{ name: "inventory", url: "http://localhost:4001" },
{ name: "accounts", url: "http://localhost:4000" }
],
debug: true,
experimental_pollInterval:3000
})
the code above will pull every 3 seconds
I don't know other ways to automatically reload the gateway other than polling.
I made a reusable docker image and i will keep updating it if new ways to reload the service emerge. For now you can use the POLL_INTERVAL env var to periodically check for changes.
Here is an example using docker-compose:
version: '3'
services:
a:
build: ./a # one service implementing federation
b:
build: ./b
gateway:
image: xmorse/apollo-federation-gateway
ports:
- 8000:80
environment:
CACHE_MAX_AGE: '5' # seconds
POLL_INTERVAL: '30' # seconds
URL_0: "http://a"
URL_1: "http://b"
You can use express to refresh your gateway's schema. ApolloGateway has a load() function that go out to fetch the schemas from implementing services. This HTTP call could potentially be part of a deployment process if something automatic is needed. I wouldn't go with polling or something too automatic. Once the implementing services are deployed, the schema is not going to change until it's updated and deployed again.
import { ApolloGateway } from '#apollo/gateway';
import { ApolloServer } from 'apollo-server-express';
import express from 'express';
const gateway = new ApolloGateway({ ...config });
const server = new ApolloServer({ gateway, subscriptions: false });
const app = express();
app.post('/refreshGateway', (request, response) => {
gateway.load();
response.sendStatus(200);
});
server.applyMiddleware({ app, path: '/' });
app.listen();
Update: The load() function now checks for the phase === 'initialized' before reloading the schema. A work around might be to use gateway.loadDynamic(false) or possibly change gateway.state.phase = 'initialized';. I'd recommend loadDyamic() because change state might cause issues down the road. I have not tested either of those solutions since I'm not working with Apollo Federation at the time of this update.
I am developing an app using Angular 7 and Apollo GraphQL client and I am trying to use the client devtools for Chrome. If I understood the documentation correctly, the only thing that I have to do is to run my app in a non-production environment and the Apollo tab will appear on the Google Chrome development tools.
Unfortunately this is not happening. The Apollo Devtools icon appears on my browser, but the Apollo tab does not appear on the devtools.
Am I missing some configuration?
I also tried to force the devtools to appear, by adding: connectToDevTools: true to my GraphQL module (code below), but this didn't solve the problem.
const uri = environment.graphqlURL; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink) {
return {
link: httpLink.create({uri}),
cache: new InMemoryCache(),
connectToDevTools: true
};
}
#NgModule({
exports: [ApolloModule, HttpLinkModule],
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}
I found out what the problem was: I was having a CORS error when I tried to use my Graphql endpoint from the Angular development server (localhost:4200), so I created a proxy that pointed to my endpoint:
{
"/graphql/*": {
"target": "https://my-endpoint/",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
And changed the Graphql URL to: http://localhost:4200/graphql. With this I was able to solve my CORS issue, however it seems that the Apollo Dev Tools also uses the /graphql URI.
So I changed my proxy configuration to:
{
"/stg_graphql/*": {
"target": "https://my-endpoint/graphql",
"secure": false,
"logLevel": "debug",
"changeOrigin": true,
"pathRewrite": {
"^/stg_graphql": ""
}
}
}
And pointed the graphql to: http://localhost:4200/stg_graphql. When I did this, everything started working.
Note: to run the development server with the proxy I am using: ng serve --proxy-config proxy.config.json.