nuxt-modules / apollo

Nuxt.js module to use Vue-Apollo. The Apollo integration for GraphQL.
https://apollo.nuxtjs.org
MIT License
929 stars 195 forks source link

Add custom link through App Config File on Nuxt3 #463

Closed manakuro closed 1 year ago

manakuro commented 1 year ago

Your use case

I expect to be able to add a custom link on Nuxt3.

The solution you'd like

Since we can't add specific code in the nuxt.config.ts, it would be great to be able to customize the link through the App Config File.

Users can create the custom link in the app.config.ts like so:

export default defineAppConfig({
  link: ({ ApolloLink, HttpLink, onError }) => {
    const serverTraceLink = new ApolloLink((operation, forward) => {
      operation.setContext(({}) => {

        return {
          headers: {
            'X-Test-Header': "Yes"
          }
        }
      })

      return forward(operation).map((data) => {
        return data
      })
    })

    const errorLink = onError((error) => {
      console.log(error)
    })

    const httpLink = new HttpLink({
      uri: "https://api.spacex.land/graphql"
    });

    return ApolloLink.from([
      serverTraceLink,
      errorLink,
      httpLink,
    ])
  }
})

Or if you want to just extend it:

export default defineAppConfig({
  link: ({ ApolloLink, errorLink, httpLink }) => {
    const serverTraceLink = new ApolloLink((operation, forward) => {
      operation.setContext(({}) => {

        return {
          headers: {
            'X-Test-Header': "Yes"
          }
        }
      })

      return forward(operation).map((data) => {
        return data
      })
    })

    return ApolloLink.from([
      serverTraceLink,
      errorLink,
      httpLink,
    ])
  }
})

In the src/runtime/plugin.ts, if there is the config file then replace it with like so:

    import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache, split, HttpLink } from "@apollo/client/core";
    import { ref, useCookie, defineNuxtPlugin, useRequestHeaders, useAppConfig } from "#imports";

    const appConfig = useAppConfig()

    const link = appConfig.link ? appConfig.link({ ApolloLink, HttpLink, onError, httpLink, errorLink, wsLink }) : ApolloLink.from([
      errorLink,
      ...(!wsLink
        ? [httpLink]
        : [
            ...(clientConfig?.websocketsOnly
              ? [wsLink]
              : [
                  split(({ query }) => {
                    const definition = getMainDefinition(query)
                    return (definition.kind === 'OperationDefinition' && definition.operation === 'subscription')
                  },
                  wsLink,
                  httpLink)
                ])
          ])
    ])

https://github.com/nuxt-modules/apollo/blob/v5/src/runtime/plugin.ts#L91-L107

The interface would be like so:

src/types.d.ts


import { ModuleOptions } from './module'
import {ApolloLink, HttpLink} from "@apollo/client";
import { onError } from '@apollo/client/link/error';

declare module '@nuxt/schema' {
  interface NuxtConfig { ['apollo']?: Partial<ModuleOptions> }
  interface NuxtOptions { ['apollo']?: ModuleOptions }

  interface AppConfigInput {
    link: (params: { ApolloLink: ApolloLink, onError: typeof onError, HttpLink: HttpLink, httpLink: ApolloLink, errorLink: ApolloLink, wsLink: ApolloLink }) => ApolloLink
  }
}

export { ErrorResponse } from './module'

Possible alternatives

No response

Additional information

No response

guendev commented 1 year ago

I think you can create a plugin to adjust the apollo link

export default defineNuxtPlugin(async (nuxtApp) => {
  const { $apollo } = useNuxtApp()
  $apollo.defaultClient.setLink(/* paste the new link here */)
}
manakuro commented 1 year ago

Nice! I didn't even know the setLink interface.

https://github.com/apollographql/apollo-client/blob/07f393adb04e2246b64852b1ade936f122253b5e/src/core/ApolloClient.ts#L629

Although the setLink can replace a whole link, I think it still needs to be able to extend the existing links.

guendev commented 1 year ago

You can access the current link via $apollo.defaultClient.link

juanmanuelcarrera commented 1 year ago

A trick for this defined here.

manakuro commented 1 year ago

Based on the nguyenshort's suggestion , we could add a custom link like this:

import { getIdToken } from '@/shared/auth'
import {provideApolloClient} from '@vue/apollo-composable'
import type {ApolloClient} from "@apollo/client/core";
import {ApolloLink, from} from "@apollo/client/core";

export default defineNuxtPlugin(({ hook }) => {
  const { clients } = useApollo()
  const defaultClient: ApolloClient<any> = (clients as any).default

  const customLink = new ApolloLink((operation, forward) => {
    return forward(operation).map((data) => {
      return data
    })
  })

  defaultClient.setLink(from([
    customLink,
    defaultClient.link,
  ]))

  // For using useQuery in @vue/apollo-composable
  provideApolloClient(defaultClient)

  hook('apollo:error', (error) => {
    console.log('error: ', error)
  })
  hook('apollo:auth', async ({ token }) => {
    token.value = await getIdToken()
  })
})
fahdarafat commented 1 year ago

Based on the nguyenshort's suggestion , we could add a custom link like this:

import { getIdToken } from '@/shared/auth'
import {provideApolloClient} from '@vue/apollo-composable'
import type {ApolloClient} from "@apollo/client/core";
import {ApolloLink, from} from "@apollo/client/core";

export default defineNuxtPlugin(({ hook }) => {
  const { clients } = useApollo()
  const defaultClient: ApolloClient<any> = (clients as any).default

  const customLink = new ApolloLink((operation, forward) => {
    return forward(operation).map((data) => {
      return data
    })
  })

  defaultClient.setLink(from([
    customLink,
    defaultClient.link,
  ]))

  // For using useQuery in @vue/apollo-composable
  provideApolloClient(defaultClient)

  hook('apollo:error', (error) => {
    console.log('error: ', error)
  })
  hook('apollo:auth', async ({ token }) => {
    token.value = await getIdToken()
  })
})

I'm trying to do the same thing to extend the current apollo link and add some custom headers. But I'm getting the defaultClient as undefined. Can you share your nuxt.config file or how do you define the default client there?

juanmanuelcarrera commented 1 year ago

Hi @fahdarafat,

Here is a solution that may work for you.

Another problem I see in the exposed solution is that you can't combine an already combined link. defaultClient.link is a link composed by the from function with other links, so you can't reuse the from function with this link.

You need to define your own links (error, auth, http...), combine it (from([...])) and add it to the default client (setLink).

manakuro commented 1 year ago

@fahdarafat Here is my nuxt.config.ts:

  modules: ['@nuxtjs/apollo'],

  apollo: {
    clients: {
      default: {
        httpEndpoint: 'https://xxxx.com/v1/graphql',
      }
    },
  },

Make sure you configure the @nuxtjs/apollo in the module.