Next.js Discord

Discord Forum

How to prevent multiple DB calls in this situation

Unanswered
Havana posted this in #help-forum
Open in Discord
HavanaOP
I am using T3 stack with:
- Drizzle
- WorkOS AuthKit
- Next JS app router

The auth/user data from AuthKit only has email address, first/last name so I wanted to store some additional data for each user like username so as a result I created a custom getUser function that wraps the AuthKit getUser function:
 
// app/_lib/getUser.ts

import { getUser as workOsGetUser } from "@workos-inc/authkit-nextjs";
import { db } from "~/server/db";
import { users } from "~/server/db/schema";

interface Options {
  ensureSignedIn?: boolean;
}

// Only use this getUser in /app/
export const getUser = async (options: Options = {}) => {
  console.log("customGetUser");
  const { ensureSignedIn } = options;
  const authUser = ensureSignedIn
    ? await workOsGetUser({ ensureSignedIn: true })
    : await workOsGetUser();

  let dbUser;
  if (authUser.user) {
    dbUser = await db.query.users.findFirst({
      where: (model, { eq }) => eq(model.authProviderId, authUser.user.id),
    });
    if (!dbUser) {
      dbUser = await db
        .insert(users)
        .values({
          authProviderId: authUser.user.id,
        })
        .returning();
    }
  }

  const singleDbUser = Array.isArray(dbUser) ? dbUser[0] : dbUser;

  return {
    user: authUser.user,
    role: authUser.role,
    dbUser: singleDbUser,
    hasRegistered: authUser.user && dbUser,
    hasUsername: singleDbUser?.username,
  };
};

2 Replies

HavanaOP
I can use this in pages and components like so:
// app/private/page.tsx
import { SignInButton } from "../_components/sign-in-button";
import { getUser } from "../_lib/getUser";

export default async function Private() {
  const { hasRegistered, hasUsername, dbUser } = await getUser({
    ensureSignedIn: true,
  });

  if (hasUsername) {
    return (
      <div>
        <h1>Private</h1>
        <p>Welcome back, {dbUser?.username}</p>

        <SignInButton />
      </div>
    );
  }

  if (hasRegistered) {
    return (
      <div>
        <h1>Private</h1>
        <p>Welcome, you must first setup a username</p>

        <SignInButton />
      </div>
    );
  }

  // console.log({ user });
  return (
    <div>
      <h1>Private</h1>
      <p>Unauthorized</p>
      <p>Shouldnt see this as it should redirect before rendering</p>
    </div>
  );
}


// app/_components/sign-in-button.tsx
import { getSignInUrl, signOut } from "@workos-inc/authkit-nextjs";
import { getUser } from "../_lib/getUser";

export async function SignInButton() {
  const { hasRegistered } = await getUser();
  const authorizationUrl = await getSignInUrl();

  if (hasRegistered) {
    return (
      <div>
        <form
          action={async () => {
            "use server";
            await signOut();
          }}
        >
          <button>Sign Out</button>
        </form>
      </div>
    );
  }

  return (
    <button>
      <a href={authorizationUrl}>Sign In</a>
    </button>
  );
}


This appears to work fine on the surface, but the problem I am facing is that if I have a page with many components that use getUser then I end up making multiple DB calls when essentially I should only need to make one DB call on page load:
// Server log
customGetUser
customGetUser <--- See this runs twice due to getUser being called in app/private/page.tsx and app/_components/sign-in-button.tsx
 GET /private 200 in 28ms
How should I structure it so that it only makes one DB call per page, or only when initial application loads?

The AuthKit getUser function alone doesn't require any extra DB calls because it gets the user information from the cookie, but in my application I would like to get some extra info from the Users table in the DB.