I have an express back-end with GraphQL that works when I go to /graphiqland manually perform some searches. My React front-end is trying to perform a search on the back-end. The following code should perform the query asynchronously:
const data = await this.props.client.query({
query: MY_QUERY,
variables: { initials: e.target.value }
});
console.log(data);
Where MY_QUERY is defined before and represents a query that I know works and has been tested on /graphiql. To do this in my React component I export it as export default withApollo(MyComponent) so that it has the client variable in the props.
In the index.js file I defined through Apollo the connection to /graphiql in order to perform the queries:
//link defined to deal with errors, this was found online
const link = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
//the httpLink to my GraphQL instance, BASE_URL is defined elsewhere
const httpLink = new HttpLink({
uri: BASE_URL,
headers: {
},
});
//here I define the client linking the GraphQL instance, the cache, and error handling
const client = new ApolloClient({
link: httpLink,
cache,
link
});
When executing the above mentioned query without the linkvariable that handles the error, I receive a 400 Bad Request from the server (ApolloError.js:37 Uncaught (in promise) Error: Network error: Response not successful: Received status code 400). Since this doesn't tell me more, here on StackOverflow and on the Apollo Web page I've found the above error declaration that outputs [Network error]: TypeError: forward is not a function. What does this error mean and how do I solve it?
Thanks!
Your client configuration has a duplicate property -- you first set the link property to your HttpLink and then set it again to your ErrorLink. This means the HttpLink is ignored completely, and you're only passing in the ErrorLink to the configuration. You're seeing that error because the ErrorLink created by onError is not meant to be used by itself. Instead, it should be chained with the HttpLink and that's what you should assign to the link property.
This page in the docs details how to correctly compose links. You can use concat but I prefer ApolloLink.from since it allows you to clearly show the order of your links:
const errorLink = onError(...)
const httpLink = new HttpLink(...)
const link = ApolloLink.from([
errorLink,
httpLink,
])
const client = new ApolloClient({
link,
cache,
})
Related
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.
I have read through a couple other posts as well as a few github issues, and I am yet to find a solution. When I logout as one user, and sign in as a different user, the new user will appear for a split second and then be replaced by the previous user's data.
Here is my attempt to go nuclear on the cache:
onClick={() => {
client
.clearStore()
.then(() => client.resetStore())
.then(() => client.cache.reset())
.then(() => client.cache.gc())
.then(() => dispatch(logoutUser))
.then(() => history.push('/'));
}}
I've tried getting the client object from both these locations (I am using codegen):
const { data, loading, error, client } = useUserQuery();
const client = useApolloClient();
Here is my Apollo client setup:
const apolloClient = new ApolloClient({
uri: config.apiUrl,
headers: {
uri: 'http://localhost:4000/graphql',
Authorization: `Bearer ${localStorage.getItem(config.localStorage)}`,
},
cache: new InMemoryCache(),
});
When I login with a new user, I writeQuery to the cache. If I log the data coming back from the login mutation, the data is perfect, exactly what I want to write:
sendLogin({
variables: login,
update: (store, { data }) => {
store.writeQuery({
query: UserDocument,
data: { user: data?.login?.user },
});
},
})
UserDocument is generated from codegen:
export const UserDocument = gql`
query user {
user {
...UserFragment
}
}
${UserFragmentFragmentDoc}`;
Following the docs, I don't understand what my options are, I have tried writeQuery, writeFragment, and cache.modify and nothing changes. The Authentication section seems to suggest the same thing I am trying.
Seems like all I can do is force a window.location.reload() on the user which is ridiculous, there has to be a way.
Ok, part of me feels like a dumb dumb, the other thinks there's some misleading info in the docs.
despite what this link says:
const client = new ApolloClient({
cache,
uri: 'http://localhost:4000/graphql',
headers: {
authorization: localStorage.getItem('token') || '',
'client-name': 'Space Explorer [web]',
'client-version': '1.0.0',
},
...
});
These options are passed into a new HttpLink instance behind the scenes, which ApolloClient is then configured to use.
This doesn't work out of the box. Essentially what is happening is my token is being locked into the apollo provider and never updating, thus the payload that came back successfully updated my cache but then because the token still contained the old userId, the query subscriptions overwrote the new data from the new user's login. This is why refreshing worked, because it forced the client to re-render with my local storage.
The fix was pretty simple:
// headerLink :: base headers for graphql queries
const headerLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
// setAuthorizationLink :: update headers as localStorage changes
const setAuthorizationLink = setContext((request, previousContext) => {
return {
headers: {
...previousContext.headers,
Authorization: `Bearer ${localStorage.getItem(config.localStorage)}`,
},
};
});
// client :: Apollo GraphQL Client settings
const client = new ApolloClient({
uri: config.apiUrl,
link: setAuthorizationLink.concat(headerLink),
cache: new InMemoryCache(),
});
And in fact, I didn't even need to clear the cache on logout.
Hope this helps others who might be struggling in a similar way.
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).
I have problem with getInitialProps method in NextJS. It is never called. This is project where I have Apollo GraphQL client for some pages and getInitialProps for other. I am not sure how to configure them correctly to work.
Apollo is working fine and fetching data as it should. Problem is that getInitialProps isn't called.
Here is my custom _app.js file
const App = ({ Component, pageProps, apollo }) => {
return (
<ApolloProvider client={apollo}>
<Component {...pageProps} />
</ApolloProvider>
)
}
const API_URL =
process.env.NODE_ENV === "development"
? "http://localhost/wordpress/index.php?graphql"
: "https://page/index.php?graphql"
export default withApollo(({ initialState }) => {
return new ApolloClient({
link: new createHttpLink({
uri: API_URL,
fetch: fetch
}),
cache: new InMemoryCache()
})
})(App, { getDataFromTree })
And here is how I call getInitialProps on page
Coupons.getInitialProps = async function() {
const res = await fetch('http://localhost:8000/data/');
const data = await res.json();
console.log(`Data fetched. Count: ${data.length}`);
return {
shows: data.map(entry => entry.show)
};
};
Also. Pages where I have Apollo fetching data doesn't need to call this REST API. Apollo pages and REST pages are totally different
This problem was fixed by following documentation on https://github.com/zeit/next.js/tree/canary/examples/with-apollo
Thing is that I wrapped whole _app in Apollo provider and right way is to wrap only pages that need Apollo in it.
Other that need getInitialProps should remain as is and call REST API in them.
I use apollo-link-error to handle graphql errors. I need to know the uri of the current link in the callback of onError. However, the signature of the callback is as follows:
function ({operation, response, graphQLErrors, networkError, forward})
It seems it is impossible to get the uri from these parameters. So do I miss something? Or do I need to use other tools to achieve this purpose?
In fact, I need to know the uri for retrying purpose (retry to request another server).
I config the client as follows,
var uriList = [uri1, uri2]
const customFetch = (uri, options) => {
const dynamicURI = getOneURIFromURIList()
return fetch(dynamicURI, options);
}
const errorLink = onError(({ networkError, operation, forward }) => {
if (needRetry(networkError)) {
// Here, if I know the URI of the terminating link, I can removed it
// from the uriList, and customFetch will not choose it again.
return forward(operation)
}
})
const link = errorLink.concat(createHttpLink({ fetch: customFetch }))
The request URL is not available as a parameter on the onError callback.
Clients only include a single terminating link -- usually an HttpLink or BatchHttpLink. The only exception to this is when we use the split function to support both a WebSocketLink and another terminating link. All that to say your client will generally have a single HttpLink and that link will have a single URL for making requests -- i.e. you'll typically have just the one request URL per client. Unless you're using a custom link or an otherwise atypical setup, given a particular client you should already have access to this URL outside the context of the onError callback.
EDIT:
I would suggest a more traditional approach to load balancing rather than trying to do this client-side (for example, using an actual load balancer or implementing the functionality with nginx). This way, your client would only have one URL to use whether for the initial request or a retry and deciding which server to use for the request would be handled by the backend.
That said, you should be able to achieve what you're trying to do by utilizing context and the split function. Something like this should work:
import { setContext } from 'apollo-link-context'
import { createHttpLink } from 'apollo-link-http'
import { split, from } from 'apollo-link'
const contextLink = setContext(() => ({
// you could return something other than the URI here, like a number
// we just need some value that's unique to each link
targetURI: getOneURIFromURIList()
}))
const optionalHttpLink1 = split(
(operation) => operation.getContext().targetURI === URI_1,
createHttpLink({ uri: URI_1 })
)
const optionalHttpLink2 = split(
(operation) => operation.getContext().targetURI === URI_2,
createHttpLink({ uri: URI_2 })
)
const optionalHttpLink3 = split(
(operation) => operation.getContext().targetURI === URI_3,
createHttpLink({ uri: URI_3 })
)
const errorLink = onError(({ operation }) => {
// call operation.getContext().targetURI to determine the URI used
})
const link = from([
contextLink,
optionalHttpLink1,
optionalHttpLink2,
optionalHttpLink3,
errorLink,
])
You'll also want to make sure you drop the customFetch option for the above to work.
Also note that split takes a second link as an optional third parameter. So with two parameters, you specify the condition and a link to use if the condition is met. With three parameters, you specify a condition, a link to use if the condition is met and a link to use if the condition is not met. All that to say if you're only working with two URIs in the example above, you can use just one split instead of three:
const httpLink = split(
(operation) => operation.getContext().targetURI === URI_1,
createHttpLink({ uri: URI_1 }),
createHttpLink({ uri: URI_2 })
)
If you have more than 2 URIs, you'll want one split per URI.