GET query missing: Apollo Client on nodejs server - graphql

I'm running an Apollo GraphQL Client on a nodejs server locally that interacts with a GraphQL endpoint hosted on the cloud. I'm getting a "GET query missing" error whenever I query that endpoint with the client, despite the fact that I also have an Angular application using Apollo Angular (which internally uses Apollo Client), which is able to successfully query the cloud endpoint. The client also works fine if I host the GraphQL endpoint locally on my machine. I'm also using the cross-fetch package as my polyfill for fetch.
Here's my code for making the GraphQL client:
public rebuildClient() {
if (this.apollo) {
this.apollo.stop();
this.apollo.clearStore();
}
this.apollo = new ApolloClient(this.configureApolloClientOptions());
}
private configureApolloClientOptions() {
const uploadLink = createUploadLink({
uri: this.serverConfig.httpsPrefix + this.serverConfig.backendDomain + this.serverConfig.graphqlRelativePath,
headers: { 'Apollo-Require-Preflight': 'true' },
fetch: fetch,
});
if (this.graphQLWsClient) this.graphQLWsClient.dispose();
this.graphQLWsClient = createClient({
url: this.serverConfig.wssPrefix + this.serverConfig.backendDomain + this.serverConfig.graphqlRelativePath,
connectionParams: {
authentication: `Basic-Root ${this.authConfig.backendRootUser.username}:${this.authConfig.backendRootUser.password}`,
},
webSocketImpl: WebSocket,
});
const webSocketLink = new GraphQLWsLink(this.graphQLWsClient);
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
const splitLink = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
webSocketLink,
uploadLink,
);
return {
link: splitLink,
cache: new InMemoryCache(),
};
}
Here's a template for the serverConfig, which is a JSON file:
{
"backendDomain": "localhost:3000",
"cdnDomain": "localhost:3000",
"cdnHttpsPrefix": "http://",
"httpsPrefix": "http://",
"wssPrefix": "ws://",
"cdnRelativePath": "/cdn",
"dynamicCdnRelativePath": "/cdn/dynamic",
"graphqlRelativePath": "/graphql",
"selfHostedPrefix": "cdn://",
"archiveCategoryId": ""
}
Note that everything works if backendDomain is set to a localhost domain. But when I set it to an actual domain, the Apollo Client breaks with the errors mentioned above.

Related

GraphQL with ApolloServer says "GET query missing." even though I have playground set to true the way another post advised

I am trying to use Postman to hit a graphQL endpoint to teach myself how it works. I have a database with user data prepopulated and two Postman routes that should work but don't work.
The requests I am trying to send via Postman:
(1) Using GraphQL mode under the Body tab
{
User {
first_name
last_name
}
}
(2) using the raw mode under the Body tab
{
user {
first_name
last_name
}
}
In both cases I have correctly set the headers Content-Type to application/graphql. So it's not that.
I found two posts about this while Googling. Both are on StackOverflow.
(1) apollo-server returning GET query missing when playground is disabled
This one says basically, "do this":
const server = new ApolloServer({
introspection: true, // i inserted this line & the next one as specified
playground: true,
typeDefs,
resolvers,
})
(2) GET query missing: Implementing GraphQL Using Apollo On an Express Server
This one references the prior link. It's also for graph-server-express, and I'm using apollo-server-fastify
I also found Apollo Graphql with Fastify who also has "GET query missing." issue but no solution. It says to downgrade to fastify v2 but that's an old answer from 2020. This is 2022, we can do better.
Again the issue is that Postman says "GET query missing." to all my requests.
My server:
async function startApolloServer(typeDefs, resolvers) {
const apolloServer = new ApolloServer({
// introspection: true,
// playground: true, // to resolve "GET query missing." in Postman
typeDefs,
resolvers,
plugins: [
fastifyAppClosePlugin(fastify),
ApolloServerPluginDrainHttpServer({ httpServer: fastify.server }),
ApolloServerPluginLandingPageGraphQLPlayground(),
],
context: ({ request, reply }) => {
//Invaluable for debugging
if (env === "development") {
console.log("GOT A REQUEST: ", request.body);
}
return { knex, reply };
},
});
await apolloServer.start();
fastify
.register(helmet)
.register(require("fastify-sensible"))
.register(require("fastify-healthcheck"))
.register(require("fastify-formbody"))
.register(apolloServer.createHandler());
await fastify.listen(serviceListeningPort);
console.log(
`🚀 Server ready at http://localhost:${serviceListeningPort}${apolloServer.graphqlPath}`
);
}
startApolloServer(typeDefs, resolvers);
Also, the updated version of Apollo Server seems to replace playground: true with ApolloServerPluginLandingPageGraphQLPlayground(), which for me enables a page that says "Loading GraphQL Playground" at the server root but never finishes loading.

Cypress not seeing graphql request

This seems to be a long lasting issue:
In cypress interface, my application cannot send any graphql request or receive any response. Because it is fetch type.
here is the network status in cypress:
But in normal browser, I actually have several graphql requests, like here:
I know there are already quite several discussions and workarounds, such as using an polyfill to solve this problem such as below:
https://gist.github.com/yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc
Cypress does not intercept GraphQL API calls
but unfortunately, they are not working in my case.
Appreciate to the help of any kinds.
p.s.: I am using cypress 8.3.0, React as the front-end, and using apollo client and apollo server for all graphql stuff.
EDIT:
samele intercept:
cy.intercept('POST', Cypress.env('backendpiUrl') + '/graphql', req => {
if (req.body.operationName === 'updateItem') {
req.alias = 'updateItemMutation';
}
});
sample cypress console:
You can see that all the requests are XHR based, no graphql's fetch request
The links are old, unfetch polyfill is no longer necessary. Since the introduction of cy.intercept(), fetch is able to be waited on, stubbed etc.
Here's the docs Working with GraphQL and an interesting atricle Smart GraphQL Stubbing in Cypress (Note route2 is an early name for intercept)
More up-to-date, posted two days ago bahmutov - todo-graphql-example
Key helper function from this package:
import {
ApolloClient,
InMemoryCache,
HttpLink,
ApolloLink,
concat,
} from '#apollo/client'
// adding custom header with the GraphQL operation name
// https://www.apollographql.com/docs/react/networking/advanced-http-networking/
const operationNameLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers }) => ({
headers: {
'x-gql-operation-name': operation.operationName,
...headers,
},
}))
return forward(operation)
})
const httpLink = new HttpLink({ uri: 'http://localhost:3000' })
export const client = new ApolloClient({
link: concat(operationNameLink, httpLink),
fetchOptions: {
mode: 'no-cors',
},
cache: new InMemoryCache(),
})
Sample test
describe('GraphQL client', () => {
// make individual GraphQL calls using the app's own client
it('gets all todos (id, title)', () => {
const query = gql`
query listTodos {
# operation name
allTodos {
# fields to pick
id
title
}
}
`
cy.wrap(
client.query({
query,
}),
)
.its('data.allTodos')
.should('have.length.gte', 2)
.its('0')
.should('deep.equal', {
id: todos[0].id,
title: todos[0].title,
__typename: 'Todo',
})
})
Please show your test and the error (or failing intercept).

How to send the result of graphql query as a file?

I am implementing a graphQL server using Apollo server. I want to send the response of the queries as a text file instead of JSON response. Can anyone help me how to do it in Apollo server.I am using NodeJS for server implementation.
If you would like to use Apollo Server instead of the vanilla graphql library, try adding a plugin into plugins config:
const server = new ApolloServer({
typeDefs,
resolvers,
// You can import plugins or define them in-line, as shown:
plugins: [
{
requestWillStart(reqCtx) {
return {
willSendResponse(ctx) {
ctx.response.http.headers.set('Content-Disposition', 'attachment; filename=result.json');
}
}
}
}
],
})
Reference: https://www.apollographql.com/docs/apollo-server/integrations/plugins/#willsendresponse
For sending File with app Express js :
app.get('/URL', (req, res) => res.download('./yourFile.txt'))
And to write your graphql query result to yourFile.txt
var fs = require("fs");
var data = "your_data_graphql";
fs.writeFile("temp.txt", data, (err) => {
if (err) console.log(err);
console.log("Successfully Written to File.");
});

How to use Apollo-Gateway with swagger-to-graphql tool?

I'm using Swagger Petstore via swagger-to-graphql npm module and able to run the GraphQL Playground for it.
graphQLSchema('./swagger.json', 'http://petstore.swagger.io/v2', {
Authorization: 'Basic YWRkOmJhc2ljQXV0aA=='
}).then(schema => {
const app = express();
app.use('/', graphqlHTTP(() => {
return {
schema,
graphiql: true
};
}));
app.listen(4001, 'localhost', () => {
console.info('http://localhost:4001/');
});
}).catch(e => {
console.log(e);
});
However, when I tried to feed the service to Apollo Gateway, it throws Error: Apollo Server requires either an existing schema or typeDefs
const gateway = new ApolloGateway({
serviceList: [
{ name: 'pet', url: 'http://localhost:4001' }
],
});
const server = new ApolloServer({
gateway,
// Currently, subscriptions are enabled by default with Apollo Server, however,
// subscriptions are not compatible with the gateway. We hope to resolve this
// limitation in future versions of Apollo Server. Please reach out to us on
// https://spectrum.chat/apollo/apollo-server if this is critical to your adoption!
subscriptions: false,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
What am I missing?
From the docs:
Converting an existing schema into a federated service is the first step in building a federated graph. To do this, we'll use the buildFederatedSchema() function from the #apollo/federation package.
You cannot provide just any existing service to the gateway -- the service must meet the federation spec. The only way to currently do that is to use buildFederatedSchema to create the service's schema. At this time, buildFederatedSchema does not accept existing schemas so federation is not compatible with any other tools that generate a schema for you. Hopefully that feature will be added in the near future.

GraphQL subscription using server-sent events & EventSource

I'm looking into implementing a "subscription" type using server-sent events as the backing api.
What I'm struggling with is the interface, to be more precise, the http layer of such operation.
The problem:
Using the native EventSource does not support:
Specifying an HTTP method, "GET" is used by default.
Including a payload (The GraphQL query)
While #1 is irrefutable, #2 can be circumvented using query parameters.
Query parameters have a limit of ~2000 chars (can be debated)
which makes relying solely on them feels too fragile.
The solution I'm thinking of is to create a dedicated end-point for each possible event.
For example: A URI for an event representing a completed transaction between parties:
/graphql/transaction-status/$ID
Will translate to this query in the server:
subscription TransactionStatusSubscription {
status(id: $ID) {
ready
}
}
The issues with this approach is:
Creating a handler for each URI-to-GraphQL translation is to be added.
Deploy a new version of the server
Loss of the flexibility offered by GraphQL -> The client should control the query
Keep track of all the end-points in the code base (back-end, front-end, mobile)
There are probably more issues I'm missing.
Is there perhaps a better approach that you can think of?
One the would allow a better approach at providing the request payload using EventSource?
Subscriptions in GraphQL are normally implemented using WebSockets, not SSE. Both Apollo and Relay support using subscriptions-transport-ws client-side to listen for events. Apollo Server includes built-in support for subscriptions using WebSockets. If you're just trying to implement subscriptions, it would be better to utilize one of these existing solutions.
That said, there's a library for utilizing SSE for subscriptions here. It doesn't look like it's maintained anymore, but you can poke around the source code to get some ideas if you're bent on trying to get SSE to work. Looking at the source, it looks like the author got around the limitations you mention above by initializing each subscription with a POST request that returns a subscription id.
As of now you have multiple Packages for GraphQL subscription over SSE.
graphql-sse
Provides both client and server for using GraphQL subscription over SSE. This package has a dedicated handler for subscription.
Here is an example usage with express.
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-sse';
// Create the GraphQL over SSE handler
const handler = createHandler({ schema });
// Create an express app serving all methods on `/graphql/stream`
const app = express();
app.use('/graphql/stream', handler);
app.listen(4000);
console.log('Listening to port 4000');
#graphql-sse/server
Provides a server handler for GraphQL subscription. However, the HTTP handling is up to u depending of the framework you use.
Disclaimer: I am the author of the #graphql-sse packages
Here is an example with express.
import express, { RequestHandler } from "express";
import {
getGraphQLParameters,
processSubscription,
} from "#graphql-sse/server";
import { schema } from "./schema";
const app = express();
app.use(express.json());
app.post(path, async (req, res, next) => {
const request = {
body: req.body,
headers: req.headers,
method: req.method,
query: req.query,
};
const { operationName, query, variables } = getGraphQLParameters(request);
if (!query) {
return next();
}
const result = await processSubscription({
operationName,
query,
variables,
request: req,
schema,
});
if (result.type === RESULT_TYPE.NOT_SUBSCRIPTION) {
return next();
} else if (result.type === RESULT_TYPE.ERROR) {
result.headers.forEach(({ name, value }) => res.setHeader(name, value));
res.status(result.status);
res.json(result.payload);
} else if (result.type === RESULT_TYPE.EVENT_STREAM) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
});
result.subscribe((data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
});
req.on('close', () => {
result.unsubscribe();
});
}
});
Clients
The two packages mentioned above have companion clients. Because of the limitation of the EventSource API, both packages implement a custom client that provides options for sending HTTP Headers, payload with post, what the EvenSource API does not support. The graphql-sse comes together with it client while the #graphql-sse/server has companion clients in a separate packages.
graphql-sse client example
import { createClient } from 'graphql-sse';
const client = createClient({
// singleConnection: true, use "single connection mode" instead of the default "distinct connection mode"
url: 'http://localhost:4000/graphql/stream',
});
// query
const result = await new Promise((resolve, reject) => {
let result;
client.subscribe(
{
query: '{ hello }',
},
{
next: (data) => (result = data),
error: reject,
complete: () => resolve(result),
},
);
});
// subscription
const onNext = () => {
/* handle incoming values */
};
let unsubscribe = () => {
/* complete the subscription */
};
await new Promise((resolve, reject) => {
unsubscribe = client.subscribe(
{
query: 'subscription { greetings }',
},
{
next: onNext,
error: reject,
complete: resolve,
},
);
});
;
#graphql-sse/client
A companion of the #graphql-sse/server.
Example
import {
SubscriptionClient,
SubscriptionClientOptions,
} from '#graphql-sse/client';
const subscriptionClient = SubscriptionClient.create({
graphQlSubscriptionUrl: 'http://some.host/graphl/subscriptions'
});
const subscription = subscriptionClient.subscribe(
{
query: 'subscription { greetings }',
}
)
const onNext = () => {
/* handle incoming values */
};
const onError = () => {
/* handle incoming errors */
};
subscription.susbscribe(onNext, onError)
#gaphql-sse/apollo-client
A companion package of the #graph-sse/server package for Apollo Client.
import { split, HttpLink, ApolloClient, InMemoryCache } from '#apollo/client';
import { getMainDefinition } from '#apollo/client/utilities';
import { ServerSentEventsLink } from '#graphql-sse/apollo-client';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const sseLink = new ServerSentEventsLink({
graphQlSubscriptionUrl: 'http://localhost:4000/graphql',
});
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
sseLink,
httpLink
);
export const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
If you're using Apollo, they support automatic persisted queries (abbreviated APQ in the docs). If you're not using Apollo, the implementation shouldn't be too bad in any language. I'd recommend following their conventions just so your clients can use Apollo if they want.
The first time any client makes an EventSource request with a hash of the query, it'll fail, then retry the request with the full payload to a regular GraphQL endpoint. If APQ is enabled on the server, subsequent GET requests from all clients with query parameters will execute as planned.
Once you've solved that problem, you just have to make a server-sent events transport for GraphQL (should be easy considering the subscribe function just returns an AsyncIterator)
I'm looking into doing this at my company because some frontend developers like how easy EventSource is to deal with.
There are two things at play here: the SSE connection and the GraphQL endpoint. The endpoint has a spec to follow, so just returning SSE from a subscription request is not done and needs a GET request anyway. So the two have to be separate.
How about letting the client open an SSE channel via /graphql-sse, which creates a channel token. Using this token the client can then request subscriptions and the events will arrive via the chosen channel.
The token could be sent as the first event on the SSE channel, and to pass the token to the query, it can be provided by the client in a cookie, a request header or even an unused query variable.
Alternatively, the server can store the last opened channel in session storage (limiting the client to a single channel).
If no channel is found, the query fails. If the channel closes, the client can open it again, and either pass the token in the query string/cookie/header or let the session storage handle it.

Resources