apollographql / apollo-server

🌍  Spec-compliant and production ready JavaScript GraphQL server that lets you develop in a schema-first way. Built for Express, Connect, Hapi, Koa, and more.
https://www.apollographql.com/docs/apollo-server/
MIT License
13.8k stars 2.03k forks source link

Empty response on graphql request #103

Closed jeroenbe closed 8 years ago

jeroenbe commented 8 years ago

Empty response on graphql request

When sending a graphql POST request to the apollo-server, I receive nothing but an empty response.

Context

Since this is my first ever project using Apollo (trying to set it up with Meteor, React, apollo-client and apollo-server using express), I'm just trying to connect a front end with the Fortunecookie api.

This is what I figured out so far:

query: {
    id: "ROOT_QUERY",
    typeName: "Query",
    selectionSet: { ... }
}
{"query":"{\n  fortuneCookie {\n    fortune\n  }\n}\n","operationName":""}

Which gave nothing in response.

Setup

Since the task I want to complete is fairly simple, my setup is too. The query I want to execute runs perfectly fine on GraphiQL, which leads me to believe my server is running as it should.

ApolloProvider

import React, {Component} from 'react'

import { ApolloProvider } from 'react-apollo'
import client from '/imports/api/apollo/client/client'

export default MainLayout = ({content}) => {
    return (
        <main id="MainLayout">
            <ApolloProvider client={client}>
                {content}
            </ApolloProvider>
        </main>
    )
}

Child Component

The {content} child set in the <ApolloProvider /> is the <TestComponent /> displayed below. This is set using kadira's FlowRouter and react-mounter, nothing special there.

import React, {Component} from 'react'
import {graphql} from 'react-apollo'
import gql from 'graphql-tag'
const TestComponentWithoutData =  class TestComponentWithoutData extends Component {
    render() {
        const {loading, data} = this.props.data
        return (
            <div className="TestComponent">
                { loading? 'loading...' : data.fortuneCookie}
            </div>
        )
    }
}

const FORTUNE = gql`{
  fortuneCookie{fortune}
}`

const withFortune = graphql(FORTUNE)
export default TestComponent = withFortune(TestComponentWithoutData)

Extra Screenshots

During a conversation on the apollo slack channel, I took some screenshots. They might provide some extra insights. The ApolloClient object:

screen shot 2016-08-20 at 23 18 42

The HTTP request (response is empty):

screen shot 2016-08-20 at 23 30 37 screen shot 2016-08-20 at 23 31 20 screen shot 2016-08-20 at 23 34 03

First an APOLLO_QUERY_INIT action is stored:

screen shot 2016-08-20 at 23 33 00

Next an APOLLO_QUERY_ERROR action:

screen shot 2016-08-20 at 23 32 38

I hope I provided enough information. Please bare with me that chances are my setup is just wrong and there is nothing funky going on with the apollo stack. This is my first project after all.

EDIT: I realise I said my server seems to be running fine, but because there isn't any response returned from my request I thought this issue belonged in the apollo-server repo.

SOLUTION: Turned out the graphql requests were sent to the meteor server in stead of the express server. There are a few solutions to this problem (listed below in the comments). You could connect directly to the express server using cors or the ApolloServer 'WebApp.connectHandlers.use(...)' or setup a proxy that redirects calls using http-proxy-middleware.

stubailo commented 8 years ago

Have you tried opening /graphiql on your server and trying to run a query from there?

helfer commented 8 years ago

Just a guess here, but could it be that you're sending the request to the route that's serving graphiql? Unrecognized token '<' kind of suggests that you're getting html, not an empty response. Do you have other queries that work fine?

Since this could be due to a lot of different reasons (error on the server, error on the client, wrong setup, etc.) it would be useful if you could create a repo with a reproduction for us. It sounds like your project is already pretty minimal, so if you can just put that in a github repo it would be fine.

jeroenbe commented 8 years ago

@stubailo Yes, the same fortuneCookie query runs perfectly fine in GraphiQL. @helfer It's very likely my setup is just wrong. I'll make a new Repo right away and share it here!

jeroenbe commented 8 years ago

@helfer I just published the project as is to a new remote: https://github.com/JeroenBe/no-response/tree/develop [DELETED] , as you said, its in super early stages anyway ;)

Tallyb commented 8 years ago

@JeroenBe the problem seems to be with your client configuration. You should use the network interface when creating a client (you created a variable but never used it).

export default client = new ApolloClient({
    networkInterface: createNetworkInterface('/graphql', {
    credentials: 'same-origin',
    }), 
    shouldBatch: false
    }); 

You need to just direct to the /graphql and not to the full path, but I am not quite sure why... anyone up here can shed some light? (in return to a good the response I will PR the documentation...)

jeroenbe commented 8 years ago

@Tallyb Thanks for the reply! It didn't really solve the problem though. I placed a screenshot of the ApolloClient object in my bugreport, as you can see the default _uri is already defined as "/graphql". I tried it with your code nonetheless, but without any results.

Does it work on your end?

jeroenbe commented 8 years ago

@helfer In regards to sending the request to graphiql, here's the code that defines both the graphql and graphiql route:

app.use('/graphql',  apolloExpress(req => ({
        schema: executableSchema,
        context: {
            Cookies: new Cookies({connector: fortuneCookieConnector})
        },
        graphiql: true
})))

app.use('/graphiql', graphiqlExpress({
    endpointURL: '/graphql'
}))

The ApolloClient uses "/graphql" as its _uri, as shown in the screenshot of the ApolloClient object above.

Tallyb commented 8 years ago

One more thing that I did is install the whatwg-fetch polyfill and import it.

On Sun, Aug 21, 2016, 16:12 Jeroen notifications@github.com wrote:

@helfer https://github.com/helfer In regards to sending the request to graphiql, here's the code that defines both the graphql and graphiql route:

app.use('/graphql', apolloExpress(req => ({ schema: executableSchema, context: { Cookies: new Cookies({connector: fortuneCookieConnector}) }, graphiql: true }))) app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

The ApolloClient uses '/graphql' as its uri, as shown in the screenshot of the ApolloClient object above.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/apollostack/apollo-server/issues/103#issuecomment-241256567, or mute the thread https://github.com/notifications/unsubscribe-auth/AHC0j3LZHxyYEuBDpytlWMpds9pVf2Iyks5qiE7QgaJpZM4JpLJ4 .

jeroenbe commented 8 years ago

@Tallyb Could you show me how you're using whatwg-fetch? I'm not sure what to do with it :s

Tallyb commented 8 years ago

npm install --save whatwg-fetch

on your client definition file (client.js) import 'whatwg-fetch';

Did you notice that on my example above I removed the localhost:4000 from the client definition?

jeroenbe commented 8 years ago

Ok, so I installed whatwg-fetch (and what since it's a peer dependency) and importing it, but I still receive the same error. If the code works on your end, would you mind making a PR to the repo I shared? (https://github.com/JeroenBe/no-response/tree/develop [DELETED])

This is how my client looks like at the moment:

export default client = new ApolloClient({
    networkInterface: createNetworkInterface('/graphql', {
        credentials: 'same-origin',
    }),
    shouldBatch: false
})
helfer commented 8 years ago

It looks like your request is going to the meteor server instead of the GraphQL server. Install the http-proxy-middleware package from npm and add these two lines somewhere in your server startup code:

import proxyMiddleware from 'http-proxy-middleware';
WebApp.rawConnectHandlers.use(proxyMiddleware('http://localhost:4000/graphql'));
jktunney commented 8 years ago

having the same problem. graphiql is working but not my front-end query. repo: https://github.com/jktunney/newgivefit/tree/apollo

Tallyb commented 8 years ago

ok - here is what I did: on client.js:

export default client = new ApolloClient({
    networkInterface: createNetworkInterface('//localhost:4000/graphql', {
    credentials: 'same-origin',
    }), 
    shouldBatch: false
    }); 

Then I added cors support as described in the docs: http://docs.apollostack.com/apollo-server/tools.html#corsSupport

so server js now has:

import cors from 'cors';

const app = express().use('*', cors());

Need also npm install cors of course (pun-intended)

jeroenbe commented 8 years ago

@helfer @Tallyb Yes! Both solutions are working!

Could you elaborate as to why these are working and which one is best practice?

Thanks btw!

helfer commented 8 years ago

You were simply sending the query to the wrong server. The GraphQL server is on port 4000, but you sent the query to port 3000, which is the Meteor server. You can either directly call the GraphQl server (CORS) or proxy through the Meteor server. Either is fine, and it probably doesn't matter for your setup.

stubailo commented 8 years ago

By the way, with the new Apollo Server you can use it with connect directly, no proxy required.

jeroenbe commented 8 years ago

@stubailo Do you mean by using the cors package as suggested by @Tallyb?

stubailo commented 8 years ago

No I'm saying you can do WebApp.connectHandlers.use('/graphql', apolloExpress(...)) and then no cors required.

Tallyb commented 8 years ago

@stubailo but using connect will still redirect calls to GraphQL server via the meteor server, which can impact load and performance, right?

jeroenbe commented 8 years ago

Oke, so now I have the following code in my server.js file:

const app = express() //running on port 4000
// ...
WebApp.connectHandlers.use('/graphql',  apolloExpress(req => ({
        schema: executableSchema,
        context: {
            Cookies: new Cookies({connector: new FortuneCookieConnector()})
        },
        graphiql: true
})))

In stead of:

app.use('/graphql',  apolloExpress(req => ({
        schema: executableSchema,
        context: {
            Cookies: new Cookies({connector: new FortuneCookieConnector()})
        },
        graphiql: true
})))

this gives me Cannot POST /graphql? on GraphiQL, when I define GraphiQL in the same way:

WebApp.connectHandlers.use('/graphiql', graphiqlExpress({
    endpointURL: '/graphql'
}))

I get following error when requesting GraphiQL: Cannot GET /graphiql

I'm guessing I have to reference the location of the actual express server, where would I do this?

stubailo commented 8 years ago

You shouldn't need express at all anymore - I'll look into this more soon.

jeroenbe commented 8 years ago

@stubailo Yes! I removed all references to express which did the trick! Thanks everyone for the responses! @stubailo @helfer @Tallyb

stubailo commented 8 years ago

Oh, glad it worked out!

stubailo commented 8 years ago

@Tallyb just saw your comment - I can't think of any reason this would have a performance disadvantage compared to using something else.

Tallyb commented 8 years ago

@stubailo my thought is that if you go via meteor server, it needs to handle your request and then redirect it to the Apollo Server, while using the direct access to the Apollo Server, does not require the Meteor Server to handle it at all. If they are on different machines, and with high load of users, it can be significant. Am I missing something here?

jktunney commented 8 years ago

@JeroenBe can you post your solution? i'm having a similar issue and it would be much appreciated. thanks!

jeroenbe commented 8 years ago

@jktunney Yea of course!

In the end I went with @stubailo's solution. In my server.js I removed all express references/dependencies. and replaced it with WebApp.connectHandlers, e.g.:

import express from 'express'
const app = express()

app.use(bodyParser.json())

app.use('/graphql', new ApolloExpress( ... ))
// ... etc etc etc

became:

WebApp.connectHandlers.use(bodyParser.json())
WebApp.connectHandlers.use('/graphql', new ApolloExpress( ... ))
// ... 

And on the client, my client is defined as such:

import ApolloClient from 'apollo-client'

export default client = new ApolloClient();

The problem in my case was the graphql requests being sent to the meteor server, not the express or graphql server. Meteor doesn't know what to do with them, hence the error. Obviously there are multiple ways of solving this (I listed them up in my initial post).

Hope this helps!

jktunney commented 8 years ago

ok, i'm still not getting it. i'm using node and express however...

here is my server:

var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var express = require('express');
var graphql = require('graphql');
var graphqlTools = require('graphql-tools');
var apollo = require('apollo-server');
var apolloExpress = apollo.apolloExpress;
var graphiqlExpress = apollo.graphiqlExpress;
var bodyParser = require('body-parser')
var proxyMiddleware = require('http-proxy-middleware');
var cors = require('cors');

//Material UI
global.navigator = { navigator: 'all' };

var schema = require('./app/data/schema');
var compiler = require('./webpack.config');

//GraphQL Stuff
var PORT = 8080;

var graphqlServer = express();

graphqlServer.use('/graphql', bodyParser.json(), new apolloExpress({
  schema: schema
}));

graphqlServer.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql',
}));

graphqlServer.listen(PORT, () => console.log(
  `GraphQL Server is now running on http://localhost:${PORT}/graphql`
));

var app = new WebpackDevServer(compiler, {
 contentBase: "/public/",
 publicPath: "/static/",
 stats: {colors: true}
});

app.use(proxyMiddleware('http://localhost:3010/graphql'));

app.use("/", express.static("static"));
app.listen(3000);
console.log("The App Server is running.")

here is my client `import React from 'react':

import { render } from 'react-dom';
import { browserHistory, IndexRoute, Router, Route } from 'react-router';

//Redux stuff
import thunkMiddleware from "redux-thunk";
import { Provider } from 'react-redux';
import { bindActionCreators, createStore, applyMiddleware } from 'redux';

//Local imports
import { queryReducer } from './reducers/reducer';
import routes from './config/routes';
import configStore from './store/configStore';

//Material UI qualms
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import injectTapEventPlugin from "react-tap-event-plugin";

import ApolloClient, { createNetworkInterface, addTypename } from 'apollo-client'
import { ApolloProvider } from 'react-apollo'

injectTapEventPlugin();

//Create store middleware
/*const createStoreWithMiddleware = applyMiddleware(thunkMiddleware)(createStore);*/
const client = new ApolloClient({
    networkInterface: createNetworkInterface('localhost:8080/graphql', {
        credentials: 'same-origin',
    }),
    shouldBatch: false
})
console.log("client");
console.log(client);
console.log("client");

const Application = () => (
  <MuiThemeProvider>
    <Router
        history={browserHistory}
        routes={routes}
     />
  </MuiThemeProvider>
);

render(
    <ApolloProvider client={client}>
        <Application />
    </ApolloProvider>, 
    document.getElementById('app')
);

here is my react component with my apollo query:

import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import GridComponent from './GridComponent';

class GridContainer extends React.Component {

    render() {
        console.log("props")
        console.log(this.props)
        const workouts = this.props.data.workouts;

        return (
            <GridComponent workouts={workouts} />
        )
    }
};

const GET_WORKOUTS = gql`
  query getWorkouts {
     workouts { title, date, time, location, author, contentSnippet, tags, day, image, avatar, id } 
  }
`;

const withWorkouts = graphql(GET_WORKOUTS);

const GridContainerWithData = withWorkouts(GridContainer)

export default GridContainerWithData;

graphiql is working: screen shot 2016-08-22 at 8 54 01 am

when i go to my graphql route in my browser it returns:

screen shot 2016-08-22 at 8 55 18 am

when i navigate to my route that renders the GridContainerWithData it shows this error in the console:

screen shot 2016-08-22 at 8 56 30 am

i've tried all the different solutions above, barring using connectHandlers (not using Meteor)... very frustrated because it seems like it should be a simple solution. i'd appreciate any help!

jeroenbe commented 8 years ago

@jktunney could you commit your latest changes to your repo? I cloned it but it seems the repo you have online is not up to date with the code you shared here.

jktunney commented 8 years ago

@JeroenBe the repo should be updated at this branch: https://github.com/jktunney/newgivefit/tree/apollo

jeroenbe commented 8 years ago

@jktunney Ok, so after running the project and logging the actual error it throws, it seems the error you receive is slightly different than the one I got: "Network error: JSON Parse error: Unexpected identifier \"Cannot\""

I'm not sure if your problem is caused by the same issue as mine

jktunney commented 8 years ago

@JeroenBe how were you able to produce that error?

jeroenbe commented 8 years ago

@jktunney the error field is part of your response. One the first render the error field is obviously undefined since the query is still loading: true. When loading: false or loading: undefined (in case of an error), the component will rerender.

on this line, you try to access your workouts, but since the query failed with an error you get thrown an exception. I removed this line and in stead logged this.props.data.error.

jktunney commented 8 years ago

very strange... when i swap this

class GridContainer extends React.Component {

    render() {
        console.log("props")
        console.log(this.props)
        const workouts = this.props.data.workouts;

        return (
            <GridComponent workouts={workouts} />
        )
    }
};

for this:

class GridContainer extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        console.log("props")
        console.log(this.props)
        const workouts = this.props.data.workouts;
        console.log("workouts")
        console.log(workouts)

        return (
            <div>
                hello world
            </div>
        )
    }
};

i get:

screen shot 2016-08-22 at 11 41 21 am

but when i try to pass "workouts" to my GridComponent component, I get: screen shot 2016-08-22 at 11 56 05 am

stubailo commented 8 years ago

I'm concerned that this thread might be related to several different issues at this point.