apollographql / apollo-fetch

:dog: Lightweight GraphQL client that supports middleware and afterware
https://www.npmjs.com/package/apollo-fetch
MIT License
570 stars 32 forks source link

Unable to override Apollo Client's default content-type 'application/json' header #39

Closed aleksdao closed 7 years ago

aleksdao commented 7 years ago

@jbaxleyiii great meeting you at the GraphQL NYC meetup last night! Really enjoyed yours and Sashko's talks on the innovation within the Apollo + GraphQL ecosystem - this schema stitching (local+remote, remote+remote) stuff is wild and exciting.

We chatted briefly yesterday about the issue I was having with overriding the default content-type application/json header that is set by Apollo Client. If you have any thoughts on the best way to resolve this, let me know! I'm happy to contribute back a PR to extend the client's flexibility

Intended outcome:

Additional context here: https://github.com/Yelp/yelp-fusion/issues/278#issuecomment-322328370

I'm attempting to make a request to the Yelp GraphQL API server by setting a content-type header of application/graphql (as suggested by the Yelp GraphQL docs for sending raw GraphQL queries, https://www.yelp.com/developers/graphql/guides/requests)

const networkInterface = createNetworkInterface({
  uri: 'https://api.yelp.com/v3/graphql',
});

networkInterface.use([
  {
    applyMiddleware(req, next) {
      if (!req.options.headers) {
        req.options.headers = {}; // Create the header object if needed.
      }

      req.options.headers.authorization = yelpAccessToken ? `Bearer ${yelpAccessToken}` : null;
      req.options.headers['content-type'] = 'application/graphql';
      req.options.headers['accept-language'] = 'en_US';
      next();
    },
  },
]);

const client = new ApolloClient({
  networkInterface,
});

query

const RestaurantListingQuery = gql`
  query {
    business(id: "garaje-san-francisco") {
      name
      id
      location {
        city
      }
    }
  }
`;

Expected Result: response from Yelp GraphQL API with valid restaurant data

{
    "data": {
        "business": {
            "name": "Garaje",
            "id": "garaje-san-francisco",
            "rating": 4.5,
            "url": "https://www.yelp.com/biz/garaje-san-francisco?adjust_creative=x7LWoNv05Hf_vNeFecFImQ&utm_campaign=yelp_api_v3&utm_medium=api_v3_graphql&utm_source=x7LWoNv05Hf_vNeFecFImQ"
        }
    }
}

Actual outcome:

Instead of overriding the default content-type: application/json header, the Apollo client appears to prepend application/json to the specified application/graphql value. This results in a concatenated content-type: application/json, application/graphql header.

screen shot 2017-08-14 at 8 27 57 pm

The result of this request is a malformed content-type header, to which the Yelp GraphQL API responds with Must provide query string.

Other observations

In Postman,

screen shot 2017-08-14 at 7 43 09 pm

Version

aleksdao commented 7 years ago

It appears this was just a mistake on my part! If I'm reading this correctly (https://github.com/apollographql/apollo-client/blob/3b5045dfe74183fe060d2027e31317d890755b99/src/transport/networkInterface.ts#L189), the Apollo client calls JSON.stringify on the my query... so I should in fact be sending the default content-type: application/json header in my request.

After removing application/graphql override, this works as expected. Thanks for your help!

stubailo commented 7 years ago

Yeah - we've made a somewhat intentional decision to not support the application/graphql content type, since it just makes server implementations and other tools more complex by adding another branch in addition to GET and POST with JSON.

However, it would be great to have at least an error printed if you try to set a different header for that - do you think that would have been helpful?

aleksdao commented 7 years ago

Hey Sashko - thx for getting back to me. Agree a dev-friendly error message would've been helpful, though I realize I sort of botched the whole setting headers thing.

I set req.options.headers['content-type'] = 'application/graphql'. For whatever reason, this gets merged with the default Content-Type header to create a Content-Type: application/json application/graphql mega-header.

On the other hand, if I set req.options.headers['Content-Type'] = application/graphql (Content-Type is capitalized), this overrides the default header as expected, and the Apollo client sends a request with Content-Type: application/graphql (I'm aware this wouldn't have worked anyways)

vvkkumawat commented 6 years ago

Hey there, i am setting content-type header of my request to 'application/keyauth.api.v1', but it's overridden Apollo Client's default content-type 'application/json' header.

const restLink = new RestLink({
  uri: process.env.REACT_APP_API_SERVER + process.env.REACT_APP_API_SERVER_VERSION,
  credentials: 'omit',
})
const authRestLink = new ApolloLink((operation, forward) => {
  operation.setContext(async ({headers}) => {
    return {
      headers: {
        ...headers,
        Accept: "application/json",
        'Content-Type': 'application/keyauth.api.v1',
        Authorization: localStorage.getItem('token') ? 'Bearer ' + localStorage.getItem('token') : ""
      }
    };
  });
  return forward(operation);
});

const link = ApolloLink.from([
  authRestLink,
  restLink,
]);

const cache = new InMemoryCache()
const client = new ApolloClient({
  link,
  cache,
})

gitissue