Open FernandoArteaga opened 2 years ago
~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.
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]));
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)
})
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?
^ 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
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"
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.
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,
}
},
},
@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?
For a Nuxt 3 app: Is it possible to change the configuration of
httpEndpoint
andwsEndpoint
once the application is built, using the environment variables and the runtime configuration?Is there a plugin that allows this behaviour?