Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to configure apollo clients using Nuxt 3 runtime config? #442

Open
FernandoArteaga opened this issue Oct 25, 2022 · 10 comments
Open

How to configure apollo clients using Nuxt 3 runtime config? #442

FernandoArteaga opened this issue Oct 25, 2022 · 10 comments

Comments

@FernandoArteaga
Copy link

For a Nuxt 3 app:
Is it possible to change the configuration of httpEndpoint and wsEndpoint once the application is built, using the environment variables and the runtime configuration?

Is there a plugin that allows this behaviour?

@timyourivh
Copy link

timyourivh commented Dec 29, 2022

I was about to ask the same for but for headers. I need a dynamic header to manipulate where the data is coming from but I cant find a way to do it right now.

Solved it by using apollo "links". They work like a middleware and allow you to modify any client attribute before making the request.

@guendev
Copy link

guendev commented Jan 7, 2023

#463 You can try my solution to rewrite the apollo link

@juanmanuelcarrera
Copy link

juanmanuelcarrera commented Feb 7, 2023

Based on @nguyenshort comments, by defining the ApolloLinks, including the HttpLink, in the project's plugin configuration, you can overwrite the default links by injecting the environment variable with the NuxtJS runtimeConfig.

/plugins/apollo.ts

import { createHttpLink, from } from '@apollo/client/core';
import { onError } from '@apollo/client/link/error';
import { setContext } from '@apollo/client/link/context';

export default defineNuxtPlugin((nuxtApp) => {
  // Get Nuxt runtimeConfig and apollo instance
  const runtimeConfig = useRuntimeConfig();
  const { $apollo } = useNuxtApp();

  // Create custom links (auth, error, http...)

  // Create an authLink and set authentication token if necessary
  const authLink = setContext(async (_, { headers }) => {
    return {
      headers: {
        ...headers,
        Authorization: `Bearer ${[...]}`,
      },
    };
  });

  const httpLink = authLink.concat(
    createHttpLink({
      uri: runtimeConfig.public.apiGraphqlUrl,
    })
  );

  const errorLink = onError((err) => {
    nuxtApp.callHook('apollo:error', err);
  });

  // Set custom links in the apollo client (in this case, the default apollo client)
  $apollo.defaultClient.setLink(from([errorLink, httpLink]));

  nuxtApp.hook('apollo:error', (error) => {
    console.error(error);
  });
});

This snippet overwrites the ApolloLinks that are created here within the apollo library.

const authLink = setContext(async (_, { headers }) => {
...

$apollo.defaultClient.setLink(from([errorLink, httpLink]));

@toddeTV
Copy link

toddeTV commented Feb 15, 2023

Based on @juanmanuelcarrera and @manakuro comments, I am using the following setup in Nuxt 3.

Used dependencies:

"devDependencies": {
  "@nuxtjs/apollo": "5.0.0-alpha.5",
  "nuxt": "3.1.1",
  "@types/node": "18.11.17",
  "graphql": "16.6.0",
  "typescript": "4.9.3"
},

File nuxt.config.ts:
Providing a fake endpoint that gets overridden later, but is needed in order to start Nuxt.

export default defineNuxtConfig({
  modules: [
    '@nuxtjs/apollo',
  ],
  runtimeConfig: {
    public: {
      // override with `.env` var `NUXT_PUBLIC_APOLLO_ENDPOINT` (oc do not use `process.env.*`)
      APOLLO_ENDPOINT: '',
    },
  },
  
  // for `@nuxtjs/apollo`
  apollo: {
    clients: {
      default: {
        httpEndpoint: '', // must be present but will be overridden in the external config TS file (see above)
      },
    },
  },
})

File /plugins/apolloConfig.ts:

import { createHttpLink, from, ApolloLink } from '@apollo/client/core'
import { onError } from '@apollo/client/link/error'
import { setContext } from '@apollo/client/link/context'
import { provideApolloClient } from '@vue/apollo-composable'

/**
 * See example: https://github.com/nuxt-modules/apollo/issues/442
 */
export default defineNuxtPlugin((nuxtApp) => {
  const envVars = useRuntimeConfig()
  const { $apollo } = nuxtApp

  // trigger the error hook on an error
  const errorLink = onError((err) => {
    nuxtApp.callHook('apollo:error', err) // must be called bc `@nuxtjs/apollo` will not do it anymore
  })

  // create an authLink and set authentication token if necessary
  // (Can not use nuxt apollo hook `apollo:auth` anymore bc `@nuxtjs/apollo` has no control anymore.)
  const authLink = setContext(async (_, { headers }) => {
    const someToken = '...'
    return {
      headers: {
        ...headers,
        Authorization: `Bearer ${someToken}`,
      },
    }
  })

  // create an customLink as example for an custom manual link
  const customLink = new ApolloLink((operation, forward) => {
    return forward(operation).map((data) => {
      return data
    })
  })

  // Default httpLink (main communication for apollo)
  const httpLink = createHttpLink({
    uri: envVars.public.APOLLO_ENDPOINT,
    useGETForQueries: true,
  })

  // Set custom links in the apollo client.
  // This is the link chain. Will be walked through from top to bottom. It can only contain 1 terminating
  // Apollo link, see: https://www.apollographql.com/docs/react/api/link/introduction/#the-terminating-link
  $apollo.defaultClient.setLink(from([
    errorLink,
    authLink,
    customLink,
    httpLink,
  ]))

  // For using useQuery in `@vue/apollo-composable`
  provideApolloClient($apollo.defaultClient)
})

@gillesgw
Copy link

gillesgw commented Mar 9, 2023

This solution works, but it seems rather hacky, given that in reality it is almost a matter of configuring apollo "from scratch", and at this point I don't really see the added value of installing nuxt apollo rather than apollo directly. The possibility to be able to change the endpoint(s) at runtime and not during the build seems to be quite common in the case of applications built once for multiple environments.

Can we consider adding this possibility to the plugin?

@rmcmk
Copy link

rmcmk commented Apr 1, 2023

^ Would love to have this feature. Our app maintains multiple deployed subgraphs, being able to hot swap clients at runtime would be a huge benefit

@gillesgw
Copy link

I've started looking at how to integrate runtime configuration. Locally with my tests, it seems to work pretty well.

I don't know if this is the best approach. @Diizzayy, could you take a look at my commits here and tell me if this seems like a possible solution? Right now I've added a few @ts-ignore comments because I haven't been able to set the runtimeConfig type.

I'm motivated to improve this solution and evolve the documentation accordingly.

With this implementation, the clients would be configurable via nuxt runtimeConfig as follows:

// nuxt.config.ts

export default defineNuxtConfig({
  modules: ['@nuxt/ui', '@nuxtjs/apollo'],
  runtimeConfig: {
    public: {
      apollo: {
        clients: {
          default: {
            httpEndpoint: 'https://my-custom-endoint.fr',
          }
        }
      }
    }
  }
}

And then if needed via an env variable (.env or system env variable):

NUXT_PUBLIC_APOLLO_CLIENTS_DEFAULT_HTTP_ENDPOINT="http://test.com/graphql"

@gillesgw
Copy link

Hello,

Can we have on update on the native implementation of this feature ? @Diizzayy , has said in my previous comment, I would be happy to help.

@TheYakuzo
Copy link

TheYakuzo commented Jun 20, 2023

This is a sample for Nuxt 3, @nuxtjs/apollo module who need to authenticate WebSocket for Subscription. ( No Bearer token in my case, used "x-hasura-admin-secret" on my server to authenticate ).

Link to apollo documentation for subscription

// plugins/apollo.js

import { createHttpLink, from, split } from "@apollo/client/core";
import { RetryLink } from "@apollo/client/link/retry";
import { getMainDefinition } from "@apollo/client/utilities";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { onError } from "@apollo/client/link/error";
import { provideApolloClient } from "@vue/apollo-composable";
import { createClient } from "graphql-ws"; // Already import with @nuxtjs/apollo module

export default defineNuxtPlugin((nuxtApp) => {

    // Optional method to get headers 
    const getHeaders = (full = false) => {
        let hasura;
        const token = localStorage.getItem("x-hasura-admin-secret")
        if (token) hasura = atob(token);
        const headers = full
            ? { headers: { "x-hasura-admin-secret": hasura.trim() || null } }
            : { "x-hasura-admin-secret": hasura.trim() || null };
        
        return headers;
    }

    const envVars = useRuntimeConfig();
    const { $apollo } = nuxtApp;

    // trigger the error hook on an error
    const errorLink = onError((err) => {
        nuxtApp.callHook("apollo:error", err); 
    });

    const retryLink = new RetryLink({
        delay: {
            initial: 300,
            max: 60000,
            jitter: true,
        },
        // eslint-disable-next-line no-unused-vars
        attempts: (count, operation, e) => {
            if (e && e.response && e.response.status === 401) return false;
            return count < 30;
        },
    });

    const httpLink = createHttpLink({
        uri: envVars.public.graphqlApi // http:// ou https://
    });

    const wsLink = new GraphQLWsLink(
        createClient({
            url: envVars.public.graphqlWss, // wss://
            lazy: true,
            connectionParams: () => ({
                headers: getHeaders()
            })
        })
    );

    const splitLink = split(
        ({ query }) => {
            const definition = getMainDefinition(query);
            return (
                definition.kind === "OperationDefinition" &&
                definition.operation === "subscription"
            );
        },
        wsLink,
        httpLink
    );

    $apollo.defaultClient.setLink(
        from([errorLink, retryLink, splitLink])
    );

    provideApolloClient($apollo.defaultClient);
});

It's possible to set a simple configuration for apollo in nuxt.config.ts because main config is in custom plugin apollo.js

// nuxt.config.ts
plugins: [
      { src: '~/plugins/apollo.js', mode: 'client' },
 ],
apollo: {
      clients: {
          default: {
              connectToDevTools: true,
              httpEndpoint: process.env.NUXT_PUBLIC_GRAPHQL_API as string,
              wsEndpoint: process.env.NUXT_PUBLIC_GRAPHQL_WSS as string,
          }
      },
  },

@harinlee0803
Copy link

@TheYakuzo Thanks for the example. It helped me to override the configuration for the default client using Nuxt runtime config variables. Could you give an example for overriding the configuration for another client (not default) when working with multiple clients?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants