DEV Community

Ramu Narasinga
Ramu Narasinga

Posted on • Edited on

How the userId is added to the tRPC ctx in LobeChat source code?

In this article, we analyse how the currently logged-in userId is added to the tRPC ctx by reviewing LobeChat source code. I recommend reading this tRPC setup to relate to the concepts explained in this article.

But before that, I want to explain how I found this. I study LobeChat source code and was working on a project that involved tRPC + Supabase in a Next.js app router. I am following the similar codebase architecture to LobeChat’s.

Image description

LobeChat uses Clerk for Authentication, the feature I was working required auth wall before making a request to the backend. Since I am following the LobeChat codebase principles, I just had to figure out how LobeChat configured authedProcedure.

When reading open-source code, I always use documentation to set my direction, otherwise I would be lost as these projects massive. This way, at the step — Create a tRPC router, you will find this code below:

import * as trpcNext from '@trpc/server/adapters/next';
import { appRouter } from '../../../server/routers/_app';
// export API handler
// @link https://trpc.io/docs/v11/server/adapters
export default trpcNext.createNextApiHandler({
 router: appRouter,
 createContext: () => ({}),
});
Enter fullscreen mode Exit fullscreen mode

createContext in this documentation returns an object but that is not the case with LobeChat’s createContext. createContext is configured when you define your route, this means we should be looking at trpc/lambda/[trpc]/route.ts

createContext in LobeChat

You will find the createContext in lambda/[trpc]/route.ts. This below code is from LobeChat:

import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import type { NextRequest } from 'next/server';

import { pino } from '@/libs/logger';
import { createContext } from '@/server/context';
import { lambdaRouter } from '@/server/routers/lambda';

const handler = (req: NextRequest) =>
  fetchRequestHandler({
    /**
     * @link https://trpc.io/docs/v11/context
     */
    createContext: () => createContext(req),

    endpoint: '/trpc/lambda',

    onError: ({ error, path, type }) => {
      pino.info(`Error in tRPC handler (lambda) on path: ${path}, type: ${type}`);
      console.error(error);
    },

    req,
    router: lambdaRouter,
  });

export { handler as GET, handler as POST };
Enter fullscreen mode Exit fullscreen mode

As you can see here, createContext is imported from @/server/context. Let’s now analyse @/server/context code. This below source code is picked from @/server/context.

/**
 * Creates context for an incoming request
 * @link https://trpc.io/docs/v11/context
 */
export const createContext = async (request: NextRequest): Promise<Context> => {
   // for API-response caching see https://trpc.io/docs/v11/caching
  const authorization = request.headers.get(LOBE_CHAT_AUTH_HEADER);
  let userId;
  let auth;
  if (enableClerk) {
    auth = getAuth(request);
    userId = auth.userId;
    return createContextInner({ authorizationHeader: authorization, clerkAuth: auth, userId });
   }
// …
Enter fullscreen mode Exit fullscreen mode

In this code snippet, if enableClerk is true, createContextInner is called with auth in parameter object. This is it, this is how you set userId in createContext. userId is returned by auth.userId. createContext

from the definition above only has to return object with the context set.

createContextInner function:

You will find this below code at Line 19 in server/context.ts

/**
 * Inner function for `createContext` where we create the context.
 * This is useful for testing when we don't want to mock Next.js' request/response
 */
export const createContextInner = async (params?: {
 authorizationHeader?: string | null;
 clerkAuth?: ClerkAuth;
 nextAuth?: User;
 userId?: string | null;
}): Promise<AuthContext> => ({
 authorizationHeader: params?.authorizationHeader,
 clerkAuth: params?.clerkAuth,
 nextAuth: params?.nextAuth,
 userId: params?.userId,
});
Enter fullscreen mode Exit fullscreen mode

About me:

Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.

I am open to work on an interesting project. Send me an email at ramu.narasinga@gmail.com

My Github - https://github.com/ramu-narasinga
My website - https://ramunarasinga.com
My Youtube channel - https://www.youtube.com/@thinkthroo
Learning platform - https://thinkthroo.com
Codebase Architecture - https://app.thinkthroo.com/architecture
Best practices - https://app.thinkthroo.com/best-practices
Production-grade projects - https://app.thinkthroo.com/production-grade-projects

References:

  1. https://github.com/lobehub/lobe-chat/blob/main/src/server/context.ts#L19

  2. https://github.com/lobehub/lobe-chat/blob/main/src/app/(backend)/trpc/lambda/%5Btrpc%5D/route.ts

  3. https://trpc.io/docs/client/nextjs/setup#3-create-a-trpc-router

  4. https://trpc.io/docs/client/react/server-components

Top comments (0)