How to set up Clerk with tRPC in app router?

Hi! I'm having trouble setting up Clerk in the t3 boilerplate using app router. I am quite confused it's the first time I'm trying the new architecture, I am doing this as an exercize and I am trying to understand how things are organized. I think I should be editting the files: - src/server/api/trpc (this is where the createTRPCCOntext is defined, so I guess I should add the auth object here): export const createTRPCContext = async ( opts: { headers: Headers }, ) => { return { db, //auth: getAuth( ???? ...opts, }; }; - src/trpc/server.ts (here createTRPCContext is wrapped in cache, and I'm not sure how to proceed adding the auth here) const createContext = cache(() => { const heads = new Headers(headers()); heads.set("x-trpc-source", "rsc"); return createTRPCContext({ headers: heads, }); }); - src/app/api/trpc/[trpc]/route.ts (here the conext is actually created I guess) const createContext = async (req: NextRequest) => { return createTRPCContext({ headers: req.headers, //auth: getAuth(req), ?? }); }; So any help understanding all this structure would be greatly appreciated! Thanks!
6 Replies
Juraj98
Juraj98•7mo ago
Hey @aspirante, I was trying to implement clerk+trpc today, and as I wanted to double check if I'm not missing something, I saw your question. I think I figured it out. I used this video by Web Dev Cody as starting point, plus I double checked with docs. First of all, you are on the right track with using getAuth in createTRPCContext. But this doesn't work, as you don't have opts.req. Instead, you should use auth() as can be seen in docs here. So now my code looks like this:
export const createTRPCContext = async (opts: { headers: Headers }) => {
return {
db,
auth: auth(),
...opts,
};
};
export const createTRPCContext = async (opts: { headers: Headers }) => {
return {
db,
auth: auth(),
...opts,
};
};
This should ensure that on publicProcedure the ctx.auth.userId is string | null. Now I just went ahead and created protectedProcedure that looks like this:
const isAuthorized = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}

return next({
ctx: { ...ctx, auth: ctx.auth },
});
});

export const protectedProcedure = t.procedure.use(isAuthorized);
const isAuthorized = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}

return next({
ctx: { ...ctx, auth: ctx.auth },
});
});

export const protectedProcedure = t.procedure.use(isAuthorized);
It's rather simple trpc middlewere, that just check if userId exists, and if not, throws an error. You can also see that I'm redefining the ctx.auth object. This ensures that ctx.auth.userId is correctly typed as string. Without it, it would be still string | null as it is in publicProcedure.
aspirante
aspiranteOP•7mo ago
Juraj98 thank you very much for the response. Its still confusing for me how tRPC works and why we have so much files to deal with it. But you helped me a lot! 😛 Its seems like magic, so this auth object is global?
Juraj98
Juraj98•7mo ago
Which auth object?
aspirante
aspiranteOP•7mo ago
The one you get with this import import { auth } from "@clerk/nextjs/server" in server/trpc Since Im using tRPC in this way, do I still need to wrap the app layout with the ClerkProvider?
Juraj98
Juraj98•7mo ago
So... I think that technically you don't need the <ClerkProvider/>, if you're only concerned with auth on server. From docs: The <ClerkProvider> component wraps your React application to provide active session and user context to Clerk's hooks and other components. I believe the auth function you use in createTRPCContext only needs middleware to be included. From [docs]:(https://clerk.com/docs/references/nextjs/auth) The auth() helper does require Middleware. But personally, I would advise you to not omit the <ClerkProvider/>. I'd be scared of forgetting I'm missing the provider few months later, and having things not work as I expect them to. You don't lose anything by including it.
aspirante
aspiranteOP•7mo ago
I see, I naively thought that the tRPC context already included all the functionalities of the <ClerkProvider,/> effectively being just a bigger, unified provider for all communications between server and client I would implement in the app. In that way, the ClerkProvider component would just be redundant. But now I see that is not the way the tRPC is working together with Clerk, thank you very much!
Want results from more Discord servers?
Add your server