NextAuth+SWRでGraphQL APIにOAuthトークン付きでリクエスト(mutation)する

3 min read読了の目安(約3500字

作ったもの

Vercel にデプロイしたもの

https://next-github-graphqlapi-sample.vercel.app/

ソース

https://github.com/TakahiroHimi/Next-GitHubGraphQLAPI-sample

GitHub OAuth App に SignIn したユーザーで、octocat さんのリアクションテスト用 Issue にリアクションできます。

SignIn 時に発行されたアクセストークンは以下のページから無効化できます。

https://github.com/settings/applications

簡単に解説

以下のソースはサンプルソースから部分的に抜粋したものです

認証機能(NextAuth)

認証機能の実装は以下の記事で解説しています。

https://zenn.dev/thim/articles/7e3fc6a67de764daf50a

上記記事に記載が無い内容としては、[...nextauth].tsscopeを設定しています。

pages/api/auth/[...nextauth].ts
export default NextAuth({
  providers: [
    Providers.GitHub({
      clientId: process.env.GITHUB_ID ?? "",
      clientSecret: process.env.GITHUB_SECRET ?? "",
      scope: "public_repo",   //👈ポイント
    }),
  ],

今回はパブリックリポジトリへのアクセス権限を設定しています(Issue へのリアクションに必要なため)。
例えばこれに加えて user 情報の読み取り権限が必要な場合は

scope: "public_repo read:user"

上記のように半角スペース区切りで設定します。
設定可能な scope の一覧はこちらから確認できます。

取得したトークンは以下で GraphQL Client に設定しています。

pages/index.tsx
const Index = () => {
  const [session, loading] = useSession();
  const [client, setClient] = useState<GraphQLClient>();

  useEffect(() => {
    if (session) {
      setClient(
        new GraphQLClient(API, {
          headers: [["Authorization", "bearer " + session.accessToken]],  //👈取得したトークンを設定
        })
      );
    }
  }, [session]);

GraphQL 実行(SWR)

SWR での GraphQL クエリ実行方法やキャッシュの Key については以下の記事で解説しています。

https://zenn.dev/thim/articles/d09cc8500d47d3216907

上記の記事に記載が無い内容として、今回はmutation(データ更新)を実行しています。
mutationの場合でもクエリの実行方法自体は変わらないのですが、
実行後にキャッシュ更新(mutate())を実行する必要があります。

pages/index.tsx
import { GraphQLClient } from "graphql-request";
import { mutate } from "swr";

/**
 * Issueへリアクションを追加する関数
 */
const addReaction = async (client: GraphQLClient, content: string) => {
  await client.request(addReactionQuery, {  //👈mutation実行
    addReactionInput: {
      subjectId: subjectId,
      content: content,
    },
  });
  void mutate([                             //👈ポイント
    getIssueReactionsQuery,
    repositoryOwner,
    repositoryName,
    issueNumber,
    content,
    reactionsLast,
  ]);
};

この時mutate()の引数には更新したいキャッシュの Key を指定します。
ここでは今リアクションしているかどうかのステータスを更新したいので、リアクション状態を取得する query 実行時useSWRの第一引数に渡しているものと同じものを設定します。

pages/reactionStatus.tsx
const ReactoinStatus: FC<Props> = ({ client, reaction }) => {
  const { data: reactionsData, error: reactionsError } = useSWR<Repository>(
    [                             //👈ポイント
      getIssueReactionsQuery,
      repositoryOwner,
      repositoryName,
      issueNumber,
      reaction,
      reactionsLast,
    ],
    (query, owner, name, number, content, last) =>
      client.request(query, {
        repositoryOwner: owner,
        repositoryName: name,
        issueNumber: number,
        reactionsContent: content,
        reactionsLast: last,
      })
  );

こうすることでmutation実行時にキャッシュを更新し、画面を最新の状態に保つことができます。
mutate 時のキャッシュの更新についてはいくつかやり方があり、以下の記事で大変詳しく紹介されていますので、実装に合わせて最適なものを選択してください。

https://zenn.dev/uttk/articles/b3bcbedbc1fd00#mutation

最後に

SWR はまだまだ日本語情報が少なく、 GraphQL を実行しているサンプルがあっても query だけで mutation は無い場合が多いです。
NextAuth も認証するだけでなく、取得した認証情報を使ってあれこれするサンプルは中々ありませんでした。
どちらも非常に便利なライブラリなので、是非いろんな方に使ってみてほしいと思い投稿しました。
この記事が困っている方の力になると嬉しいです。