Apollo-client: Error 400: Bad Request (message: Must provide document)

Created on 26 Jan 2017  Â·  9Comments  Â·  Source: apollographql/apollo-client

Hi all. I have pretty simple graphql-server:

import express from 'express'
import bodyParser from 'body-parser'
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express'
import { makeExecutableSchema, addResolveFunctionsToSchema } from 'graphql-tools'

import Schema from './schema'
import Resolvers from './resolvers'

const executableSchema = makeExecutableSchema({
  typeDefs: Schema,
  printErrors: true,
});

addResolveFunctionsToSchema(executableSchema, Resolvers)

const PORT = 3000;
const app = express();

app.use('/graphql', bodyParser.json(), graphqlExpress(
  {
    schema: executableSchema,
    resolvers: Resolvers,
  },
))
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

app.listen(PORT, () => console.log('Server is running!'))

With graphiql tool everything working correctly: I can make requests and receive expected results. But when I am trying to send request with apollo-client, something wrong. Here is my client code:

import ApolloClient, { createNetworkInterface } from 'apollo-client';
import gql from 'graphql-tag';

const apolloClient = new ApolloClient({
  networkInterface: createNetworkInterface({
    uri: 'http://localhost:3000/graphql',
    opts: {
      mode: 'no-cors',
    },
  }),
});

const query = gql`
  {
    polls {
      title
    }
  }
`

apolloClient.query({
  query,
}).then(result => {
  console.log(result)
})

Here is errors:

123

Here is request details and response:
2017-01-26 18 11 45
2017-01-26 18 11 55

I am using next versions:

    "apollo-client": "^0.8.0",
    "graphql": "^0.9.0",
    "graphql-server-express": "^0.5.2",
    "graphql-tag": "^1.2.3",
    "graphql-tools": "^0.9.2",

Most helpful comment

Sorry to keep bouncing you back and forth @semanser, but I took a look at your test case and I think this is a graphql-server issue after all 😊

As you can see in your “request details and response” image, the Content-Type header is text/plain. This will lead to an error on the server as it expects application/json. However, if we look at the code, Apollo Client explicitly sets the Content-Type header to application/json, and if you run a minimum reproduction of the fetch in the console you get the same error. Here is what I ran in the developer console:

fetch('http://localhost:3000/graphql', {
  method: 'POST',
  mode: 'no-cors',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: '{ users { name } }' }),
})

It got the same error. So I opened up GraphiQL and ran the same code. The same error occurred. When you remove mode: 'no-cors' in the GraphiQL error console, then the error goes away! However, you can’t remove mode: 'no-cors' in the app.

My hypothesis to what’s happening is that the browser makes a HEAD request to the GraphQL server to get the CORS options, but the server just returns a 405 saying only GET and POST requests are allowed. So the browser isn’t told to allow the Content-Type header though the CORS Access-Control-Allow-Headers header.

So we have two options. We could either add CORS headers like Access-Control-Allow-Headers to graphql-server, or you could add the CORS headers yourself. I like the latter option best because we don’t want to introduce security vulnerabilities in people’s servers by including CORS headers when the user is unsuspecting. To add CORS headers yourself, just do the following:

app.head('/graphql', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:9000');
  res.header('Access-Control-Request-Method', 'GET, POST');
  res.header('Access-Control-Allow-Headers', 'Origin, Accept, Content-Type, Content-Length');
  res.end();
});


or to avoid the roundtrip required by CORS you could proxy your API using Webpack Dev Server by changing your configuration in webpack.config.js to:

module.exports = {
  ...
  devServer: {
    contentBase: './app',
    compress: true,
    port: 9000,
    historyApiFallback: {
      index: 'index.html',
    },
    proxy: {
      '/graphql': {
        target: 'http://localhost:3000/graphql',
        secure: false,
      },
    },
  },
};


and then change your client declaration to:

const apolloClient = new ApolloClient({
  networkInterface: createNetworkInterface({
    uri: 'http://localhost:9000/graphql',
  }),
});

Note how the port is now 9000 and there is no mode: 'no-cors'.

I personally like the proxy method best.


Some general purpose CORS resources:

We hit this issue early in PostGraphQL. Our solution there was to provide an option to enable CORS headers for users who wanted to opt in. If you’d like more information about our solution there, here are some resources:

All 9 comments

This looks like a graphql-server issue. If you haven’t already could you open against that repo?

You'll see that error when you're not passing the query/mutation to graphql-server. This is definitely an error on the server and not on the client.

@helfer Ok, so I have created simple repo to reproduce this error. I think you can reopen this issue again :)
https://github.com/semanser/graphql-test
Please follow steps in the readme file to run the server.

Sorry to keep bouncing you back and forth @semanser, but I took a look at your test case and I think this is a graphql-server issue after all 😊

As you can see in your “request details and response” image, the Content-Type header is text/plain. This will lead to an error on the server as it expects application/json. However, if we look at the code, Apollo Client explicitly sets the Content-Type header to application/json, and if you run a minimum reproduction of the fetch in the console you get the same error. Here is what I ran in the developer console:

fetch('http://localhost:3000/graphql', {
  method: 'POST',
  mode: 'no-cors',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ query: '{ users { name } }' }),
})

It got the same error. So I opened up GraphiQL and ran the same code. The same error occurred. When you remove mode: 'no-cors' in the GraphiQL error console, then the error goes away! However, you can’t remove mode: 'no-cors' in the app.

My hypothesis to what’s happening is that the browser makes a HEAD request to the GraphQL server to get the CORS options, but the server just returns a 405 saying only GET and POST requests are allowed. So the browser isn’t told to allow the Content-Type header though the CORS Access-Control-Allow-Headers header.

So we have two options. We could either add CORS headers like Access-Control-Allow-Headers to graphql-server, or you could add the CORS headers yourself. I like the latter option best because we don’t want to introduce security vulnerabilities in people’s servers by including CORS headers when the user is unsuspecting. To add CORS headers yourself, just do the following:

app.head('/graphql', (req, res) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:9000');
  res.header('Access-Control-Request-Method', 'GET, POST');
  res.header('Access-Control-Allow-Headers', 'Origin, Accept, Content-Type, Content-Length');
  res.end();
});


or to avoid the roundtrip required by CORS you could proxy your API using Webpack Dev Server by changing your configuration in webpack.config.js to:

module.exports = {
  ...
  devServer: {
    contentBase: './app',
    compress: true,
    port: 9000,
    historyApiFallback: {
      index: 'index.html',
    },
    proxy: {
      '/graphql': {
        target: 'http://localhost:3000/graphql',
        secure: false,
      },
    },
  },
};


and then change your client declaration to:

const apolloClient = new ApolloClient({
  networkInterface: createNetworkInterface({
    uri: 'http://localhost:9000/graphql',
  }),
});

Note how the port is now 9000 and there is no mode: 'no-cors'.

I personally like the proxy method best.


Some general purpose CORS resources:

We hit this issue early in PostGraphQL. Our solution there was to provide an option to enable CORS headers for users who wanted to opt in. If you’d like more information about our solution there, here are some resources:

@calebmer Thanks a lot for explanation! I have tried both variants but it's still doesn't work and that's very strange... I will continue to work to find solution for this problem.

Hey there,
Are there any solutions yet?
I've got the same problem. :-(

@kurmeroli The first solution doesn't work for me. I tried with:

$ npm install cors --save

with

var express = require('express')
var cors = require('cors')
var app = express()

app.use(cors())

And works fine. The same for ES6:

import cors from 'cors';

let app = express();
app.use(cors({}));

More: https://github.com/expressjs/cors

Issue solved me by adding CORS headers

const corsOptions = {
origin(origin, callback){
callback(null, true);
},
credentials: true
};
graphQLServer.use(cors(corsOptions));
var allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
}
graphQLServer.use(allowCrossDomain);

Was this page helpful?
0 / 5 - 0 ratings