apollo-link-http does not respect a GET method override

Created on 12 Nov 2017  ·  20Comments  ·  Source: apollographql/apollo-link

As per the docs, the fetchOptions option allows us to set the method of the request to GET. I'd expect that after doing so, the query would be included as a query string parameter rather than the request body; Chrome throws an error if you try to make a GET request with a body.

Intended outcome:
A query string should be constructed if the request method is GET.

Actual outcome:
The request body is used.

How to reproduce the issue:

client.query({
  context: {
    fetchOptions: {
      method: 'GET',
    },
  },
  query: QUERY,
  variables: { query },
})
enhancement

Most helpful comment

@szdc @Outlaw11A @arackaf here is how to do it:

const customFetch = (uri, options) => {
  const { body, ...newOptions } = options;
  const queryString = objectToQuery(JSON.parse(body));
  requestedString = uri + queryString;
  return fetch(requestedString, newOptions);
};
const link = createHttpLink({
  uri: "data",
  fetchOptions: { method: "GET" },
  fetch: customFetch
});

All 20 comments

@szdc yep this definitely doesn't work! Would you be open to adding it? Something like https://github.com/apollographql/apollo-link/issues/257 may be a good solution as well to transform the request before its sent?

cc @arackaf @outlaw11a @puttyman who all have interest in this! Should be a pretty small change to add in!

I'll add it if nobody else wants to.

@szdc @arackaf so turns out this is already possible! I'll push up a test and docs soon showing how it can be done without internal changes

@szdc @Outlaw11A @arackaf here is how to do it:

const customFetch = (uri, options) => {
  const { body, ...newOptions } = options;
  const queryString = objectToQuery(JSON.parse(body));
  requestedString = uri + queryString;
  return fetch(requestedString, newOptions);
};
const link = createHttpLink({
  uri: "data",
  fetchOptions: { method: "GET" },
  fetch: customFetch
});

@jbaxleyiii that looks awesome! Just curious from where objectToQuery is coming? Is there a util on npm for that? I imagine it'd be pretty easy to just roll your own if needed - basically map Object.keys onto

escapeUriComponnt(`${k}=${obj[k]}`).join("&");

or something similar

https://www.apollographql.com/docs/link/links/http.html#Sending-GET-requests-custom-fetching

@arackaf see the comment:

// turn the object into a query string, try object-to-querystring package

https://github.com/Cyberlane/object-to-querystring

@jbaxleyiii it works! It's amazing!

I just have a problem.

The querystring created:

http://localhost/graphql?operationName=AllPlayersQuery&variables=%5Bobject%20Object%5D&query=query%20All...

contains this: variables=%5Bobject%20Object%5D which is a problem.

How to fix this?

That looks like standard URI serialization - don't think you can avoid it - why's it a problem?

Standard URI? This: %5Bobject%20Object%5D?

encodeURIComponent('books(title:"Hello World"){Book{title}}')

books(title%3A%22Hello%20World%22)%7BBook%7Btitle%7D%7D

My query in Apollo is:

query AllPlayers {
  players {
    id
    name
    surname
}

and so I have the problem with [Object object] variables empty with the code posted by @jbaxleyiii.

Here's a barebones react app (created using create-react-app) that fetches via GET and works - see index.js: https://github.com/usefulio/example-graphql-via-get-react-app

@johnunclesam obviously variables isn't serialized before encoding. i simply tuned my custom fetch method (obtained from the example repo) by adding a conditional JSON.stringify:

const customFetch = (uri, options) => {
  const {body, ...newOptions} = options;
  const parsedBody = JSON.parse(body);
  const command = omitBy(parsedBody, isEmpty);

  if (command.variables) {
    command.variables = JSON.stringify(command.variables);
  }

  const requestedString = uri + '?' + queryString.stringify(command);

  return fetch(requestedString, newOptions);
};

So what is useGETForQueries used for? Why the custom fetch is not builtin when useGETForQueries=true?
How do I differentiate query and mutation? I only want to use GET for query (for DNS cache).

This is an old issue that predates useGETForQueries, which was added a bit after this was fixed by https://github.com/apollographql/apollo-link/pull/510

umm, useGETForQueries seems to not be working.

Specifically on this line there seems to be a bug:

https://github.com/apollographql/apollo-link/blob/f94b1c6b984619ec1ef68cca3224be788ea140e4/packages/apollo-link-http/src/httpLink.ts#L41

let {
    uri = '/graphql',
    // use default global fetch if nothing passed in
    fetch: fetcher,
    includeExtensions,
    useGETForQueries,
    ...requestOptions
  } = linkOptions;

should be something like:

let {
    uri = '/graphql',
    // use default global fetch if nothing passed in
    fetch: fetcher,
    includeExtensions,
    fetchOptions,
    ...requestOptions
  } = linkOptions;

let {
  useGETForQueries
} = fetchOptions;

What makes you say that? How are you calling createHttpLink?

My observation above was from stepping through the code with a debugger, but I'd have to do it over again to reproduce.

The code we ended up settling on is this:

import omitBy from 'lodash.omitby';
import isEmpty from 'lodash.isempty';

const graphQLUrl = 'http://the.server.com/graphql';

const customFetch = (uri, options) => {
  const { body, credentials, headers, ...newOptions } = options;
  let fetchUri = uri;
  if (body) {
    const parsedBody = JSON.parse(body);
    const command = omitBy(parsedBody, isEmpty);
    fetchUri = uri + "?" + queryString.stringify(command);
  }
  return fetch(fetchUri, newOptions);
};

const link = createHttpLink({
  uri: graphQLUrl,
  fetchOptions: { method: "GET" },
  fetch: customFetch
});

I guess my question is, if you were passing useGETForQueries inside fetchOptions instead of directly inside createHttpLink, then that would be the behavior you would observe, but it would also not be how the API works. I'd suggest giving it a shot again!

Was this page helpful?
0 / 5 - 0 ratings