Apollo-link: Issue using docker compose urls

Created on 30 Dec 2017  路  12Comments  路  Source: apollographql/apollo-link

Intended outcome:

I had my app running using link: createHttpLink({ uri: process.env.GRAPHQL_ENDPOINT, fetch }),

here my endpoint is GRAPHQL_ENDPOINT = 'http://web-api:5000/graphql'

since my docker-compose file looks like this:

  web-api:
    container_name: 'web-api'
    build: './services/web-api'
    ports:
      - '5000:5000'
    environment:
      - NODE_ENV=${NODE_ENV}
  web-app:
    container_name: 'web-app'
....

I always this format for urls on docker-compose:
https://docs.docker.com/docker-cloud/apps/service-links/#service-link-environment-variables

The actual outcome that I got was

react-apollo.browser.umd.js?df32:545 Unhandled (in react-apollo:Apollo(AuthHandler)) Error: Network error: Failed to fetch
    at new ApolloError (webpack-internal:///48:36:28)
    at ObservableQuery.currentResult (webpack-internal:///44:94:24)
    at GraphQL.dataForChild (webpack-internal:///74:540:62)
    at GraphQL.render (webpack-internal:///74:590:33)
    at callMethod (webpack-internal:///160:511:12)
    at GraphQL.eval [as render] (webpack-internal:///160:522:12)
    at renderComponent (webpack-internal:///9:694:24)
    at GraphQL.forceUpdate (webpack-internal:///9:927:3)
    at GraphQL.forceRenderChildren (webpack-internal:///74:508:26)
    at next (webpack-internal:///74:483:27)

I tried to change it to use localhost:5000 instead but docker doesn't like that so right now im working w/o docker (with localhost) and works just fine, but I was wondering if you tried to use this with docker-compose

bug

Most helpful comment

I got ideas of workaround from this article Get GraphQL Apollo to Work With NextJS Server Side Rendering Within Docker

I also managed to implement my own hacky way:

...
function create(initialState, { getToken, fetchOptions }) {
  let apiEndpoint;
  if (!process.env.BROWSER) {
    global.window = {}; // Temporarily define window for server-side
    apiEndpoint = 'http://backend:8030/data-endpoint/';
  } else {
    apiEndpoint = 'http://localhost:8030/data-endpoint/';
  }

  const httpLink = createHttpLink({
    uri: apiEndpoint,
    credentials: 'include',
    fetchOptions
  });
...

For now its working.

Updates

If above does not work use this

...
function create(initialState, { getToken, fetchOptions }) {
  let apiEndpoint;
  if (typeof window !== 'undefined' && process.env.NODE_ENV == 'development') {
    apiEndpoint = process.env.DEV_API_URI_CLIENT;
  } else {
    apiEndpoint = process.env.DEV_API_URI_SSR;
  }

  const httpLink = createHttpLink({
    uri: apiEndpoint,
    credentials: 'include',
    fetchOptions
  });
...

All 12 comments

I just ran into this problem as well and its been a showstopper so far... Any idea what is causing this to happen?

I have been thinking more about this issue and I don't think that this is the fault of apollo-link. I am pretty sure this happens because the site is served to your browser and then the browser is trying to connect to resolve the web-api through DNS, which it cannot since it is outside of the docker container itself.

So you would need to open the port on the API container and then provide a way to point to it from a browser.

@lucas-aragno do you think this is a valid assessment of what is hapenning?

Actually now that you say that I think you are right, I been checking all my other projects using docker compose and are using sort of the same strategy (setting up the urls to API outside docker-compose discovery)

Yeah it works if your service needs to connect to another container from inside docker but not from the outside. Can you close this issue then if it is resolved?

@lucas-aragno Can you elaborate how you managed to solve the issue? I am running a multi-container setup linking them to each other and my client should connect to http://graphql-server:4000/ from the client container to the graphql-server container, but I am getting a net::ERR_NAME_NOT_RESOLVED error. I can easily connect from the shell to the server, so in fact it seems to be the browser causing issues here.

It's because the browser is outside of the container so it is not on the same network. You need to expose the graphql endpoint to the environment outside of docker

I do have it exposed, but obviously a setup such as the following won't work:

const httpLink = createHttpLink({
    uri: `http://localhost:4000`,
});

Frankly, I'd have to replace the localhost with the actual host Docker is running on, but I don't see a way to detect the host without hardcoding it since this code, which gets executed in the browser, is not able to know anything from the server.

right, but if the docker container is exposing it to the outside network then it is out of control of both docker and apollo, if you're really managing too many hosts then you have to use some other system to manage urls which can be passed to docker through environment variables or something similar

Indeed, I just used the following fallback assuming both client and server are hosted on the same host:

const httpLink = createHttpLink({
    uri: `http://${window.location.hostname}:4000`,
});

Not the prettiest solution, but in my current pet project it works and suffices so far.

I got ideas of workaround from this article Get GraphQL Apollo to Work With NextJS Server Side Rendering Within Docker

I also managed to implement my own hacky way:

...
function create(initialState, { getToken, fetchOptions }) {
  let apiEndpoint;
  if (!process.env.BROWSER) {
    global.window = {}; // Temporarily define window for server-side
    apiEndpoint = 'http://backend:8030/data-endpoint/';
  } else {
    apiEndpoint = 'http://localhost:8030/data-endpoint/';
  }

  const httpLink = createHttpLink({
    uri: apiEndpoint,
    credentials: 'include',
    fetchOptions
  });
...

For now its working.

Updates

If above does not work use this

...
function create(initialState, { getToken, fetchOptions }) {
  let apiEndpoint;
  if (typeof window !== 'undefined' && process.env.NODE_ENV == 'development') {
    apiEndpoint = process.env.DEV_API_URI_CLIENT;
  } else {
    apiEndpoint = process.env.DEV_API_URI_SSR;
  }

  const httpLink = createHttpLink({
    uri: apiEndpoint,
    credentials: 'include',
    fetchOptions
  });
...

I'll try this out

This is indeed because the browser cannot resolve the container hostname. I'm experiencing the issue myself atm.

That said, I hate to bump this after so long, but I've scoured SO and it seems this issue still hasn't been solved. Did you guys ever find a better solution to this, or is it the aforementioned workarounds for now?

Edit: Btw, the workaround here depends on third-party solutions e.g. process.browser is deprecated, hence @NgatiaFrankline 's updated solution. Still, this could be unpredictable.
Nextjs' isomorphic methods have req and res objects that are extant on the context object only server-side, so you could check for those and call your endpoints dynamically.

However, the most elegant solution I've found is to do this:

In next.config.js:

module.exports = {
  serverRuntimeConfig: {
    // Will only be available on the server side
    URI: 'protocol:your-docker-uri:port'
  },
  publicRuntimeConfig: {
    // Will be available on both server and client
    URI: 'protocol:localhost:port'
  }
}

Note: It is important to remember to include the protocol. Easy to forget and can be a nightmare to debug.

In pages/index.js:

import getConfig from 'next/config';
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const API_URI = serverRuntimeConfig.apiUrl || publicRuntimeConfig.apiUrl;

const Index = ({ json }) => <div>Index</div>;

Index.getInitialProps = async () => {
       ...
       const res = await fetch(`${API_URI}/endpoint`);
       ...
}
Was this page helpful?
0 / 5 - 0 ratings