typescriptnext.jstrpc

TRPC how to dynamically set context using server client


I'm following the tRPC docs on how to create a caller for server side calls but how are you suppose to set this value dynamically when used in your NextJS 13 pages?

This is what my context.ts looks like:

import { cookies } from "next/headers";
import * as jose from "jose";
import jwt from "jsonwebtoken";
import { inferAsyncReturnType } from "@trpc/server";

interface ContextUser {
    id: number;
    username: string;
    token: string;
}

export async function createContext() {
    async function getUserFromCookie(): Promise<ContextUser | null> {
        var token = cookies().get("jwt");

        if (!token?.value) return null;

        const secret = new TextEncoder().encode(process.env.JWT_SECRET);

        try {
            await jose.jwtVerify(token.value, secret);
        } catch (error) {
            return null;
        }

        const jwtPayload = jwt.decode(token.value) as any;

        return {
            id: jwtPayload.nameid,
            username: jwtPayload.unique_name,
            token: token.value,
        };
    }

    const user = await getUserFromCookie();

    return {
        user,
    };
}

export type Context = inferAsyncReturnType<typeof createContext>;

I have middleware to check that a user is set and I export a authorised procedure:

import { TRPCError, initTRPC } from "@trpc/server";
import { Context } from "./context";

const t = initTRPC.context<Context>().create();

export const router = t.router;
export const publicProcedure = t.procedure;

const authorised = t.middleware(async ({ ctx, next }) => {
    if (!ctx.user) throw new TRPCError({ code: "UNAUTHORIZED" });

    return next();
});

export const authorisedProcedure = t.procedure.use(authorised);

In my server.ts i'm exporting a server client like this:

import { appRouter } from "@/server";

export const trpcServerClient = appRouter.createCaller({
    user: { id: 1, username: "foo", token: "bar" },
});

When I call a procedure using the trpcServerClient with a valid cookie I can see by debugging the createContext that the token is present and it set the context, but in the procedure the ctx will be user: { id: 1, username: "foo", token: "bar" }.

My question then is how are you suppose to dynamically set the context when using a server client, when you have to pass a value for your context in the createCaller function?


Solution

  • import { createContext } from "@/server/context"
    import { appRouter } from "@/server";
    
    export const trpcServerClient = appRouter.createCaller(await createContext());
    

    (note that using await at top level may require changing your tsconfig target if you are still using "es5")