apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
358 stars 25 forks source link

async registerApolloClient ? #195

Open Dakuan opened 2 months ago

Dakuan commented 2 months ago

Be really handy if registerApolloClient was async...here's what im trying to do

import { HttpLink } from "@apollo/client";
import {
  NextSSRInMemoryCache,
  NextSSRApolloClient,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
import { runWithAmplifyServerContext } from "../amplify/amplifyServerUtils";
import { fetchAuthSession } from "aws-amplify/auth/server";
import { cookies } from "next/headers";

export const { getClient } = registerApolloClient(async () => {
  const token = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      const session = await fetchAuthSession(contextSpec);
      return session.tokens?.accessToken.toString();
    },
  });
  const httpLink = new HttpLink({
    uri: "http://localhost:4000/graphql",
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache(),
    link: httpLink,
  });
});

all my auth its handled by aws congnito / amplify. I can get my jwts using the amplify utils, and them pass them to the API. But, its an async process, so needs to live in an async fn...

Dakuan commented 2 months ago

this is my workaround, ugly but works:

import { HttpLink } from "@apollo/client";
import {
  NextSSRInMemoryCache,
  NextSSRApolloClient,
} from "@apollo/experimental-nextjs-app-support/ssr";
import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc";
import { runWithAmplifyServerContext } from "../amplify/amplifyServerUtils";
import { fetchAuthSession } from "aws-amplify/auth/server";
import { cookies } from "next/headers";

export const getClient = async () => {
  const token = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      const session = await fetchAuthSession(contextSpec);
      return session.tokens?.accessToken.toString();
    },
  });
  const { getClient } = registerApolloClient(() => {
    const httpLink = new HttpLink({
      uri: "http://localhost:4000/graphql",
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    return new NextSSRApolloClient({
      cache: new NextSSRInMemoryCache(),
      link: httpLink,
    });
  });

  return getClient();
};
phryneas commented 2 months ago

That sounds like a reasonable request - I'll take a look at that in a bit, but right now we're in the middle of a big refactor we have to finish first (#189, #193) - but I'll get to it after that :)

jerelmiller commented 2 months ago

@Dakuan is there any reason you couldn't use the setContext link for this? This handles async functions and you can set headers that will be sent in the request from this link.

const tokenLink = setContext(async () => ({
  headers: { 
    Authorization: `Bearer ${await getToken()}`
  }
})
Dakuan commented 2 months ago

yeah, thats what i tried originally but it complained about client code running on the server

jerelmiller commented 2 months ago

I'm curious how you set that up. This doesn't have anything to do with React and is part of the Apollo Client link chain. Did you perhaps try and use React context for this? That is something completely different 🙂

Here is a more full example to show what I mean:

import { setContext } from "@apollo/client/link/context";

async function getToken() {
  return runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      const session = await fetchAuthSession(contextSpec);
      return session.tokens?.accessToken.toString();
    },
  });
}

export const { getClient } = registerApolloClient(() => {
  const contextLink = setContext(async () => ({
    headers: {
      Authorization: `Bearer ${await getToken()}`,
    }   
  }));

  const httpLink = new HttpLink({
    uri: "http://localhost:4000/graphql",
  });

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache(),
    link: contextLink.concat(httpLink),
  });
});