Vilian
Vilian10mo ago

nextjs app router `fetchRequestHandler`'s createContext doesn't run.

Hey there, I have the following code:
// /src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '~/trpc/root';
import { ENV } from '~/env';
import { createTRPCContext } from '~/trpc/trpc';

const handler = (req: Request) =>
fetchRequestHandler({
req,
router: appRouter,
endpoint: '/api/trpc',
onError: ({ path, error }) => {
if (ENV.NODE_ENV !== 'development') return;
console.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message} `
);
},
createContext: createTRPCContext,
});

export { handler as GET, handler as POST };
// /src/app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { appRouter } from '~/trpc/root';
import { ENV } from '~/env';
import { createTRPCContext } from '~/trpc/trpc';

const handler = (req: Request) =>
fetchRequestHandler({
req,
router: appRouter,
endpoint: '/api/trpc',
onError: ({ path, error }) => {
if (ENV.NODE_ENV !== 'development') return;
console.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message} `
);
},
createContext: createTRPCContext,
});

export { handler as GET, handler as POST };
13 Replies
Vilian
Vilian10mo ago
// /src/trpc/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import superjson from 'superjson';
import { ZodError } from 'zod';
import { getServerAuthSession } from '~/auth/auth';

export const createTRPCContext = async () => {
console.log('Context');
return await getServerAuthSession();
};

const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});

export const createTRPCRouter = t.router;

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
console.log(ctx);
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
});

export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
// /src/trpc/trpc.ts
import { initTRPC, TRPCError } from '@trpc/server';
import superjson from 'superjson';
import { ZodError } from 'zod';
import { getServerAuthSession } from '~/auth/auth';

export const createTRPCContext = async () => {
console.log('Context');
return await getServerAuthSession();
};

const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
});

export const createTRPCRouter = t.router;

const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
console.log(ctx);
if (!ctx.session?.user) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
});

export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
When i invoke any trpc endpoint with an http request, the request goes through, but the context does not run. I'm pretty sure the createTRPCContext should be invoked on every request/batch of requests
sachin
sachin10mo ago
are you passing createContext to fetchRequestHandler ah nvm it’s in the post body this is definitely a bug can you create an issue with a minimal reproduction?
Vilian
Vilian10mo ago
I'll try to Hey @sachinraja sorry for the ping, however, do you guys have an example tRPC with react 13 app router ?
sachin
sachin10mo ago
GitHub
trpc/examples/.experimental/next-app-dir at main · trpc/trpc
🧙‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy. - trpc/trpc
Vilian
Vilian10mo ago
Last question: Are you sure what i sent above is a bug? Should the createContext run on every single request made to trpc ?
sachin
sachin10mo ago
yes
Vilian
Vilian10mo ago
What if it's used with appRouter.createCaller?
sachin
sachin10mo ago
yes but you have to pass createContext to that too
Vilian
Vilian10mo ago
Well there were some example with appRouter.createCaller(await createContext()) But as we know, this means top level await... which is not supported afaik.
sachin
sachin10mo ago
you shouldnt be calling createCaller at the top level
Vilian
Vilian10mo ago
Do you have an example/snippet you could link me to see?
sachin
sachin10mo ago
what are you using it for
Vilian
Vilian10mo ago
Well nothing so far. I'm very new to react space and i've been meaning to setup myself a nextjs app router project template to use I'm trying to understand how the whole structure maps out and works under the hood.
More Posts
"Unable to transform response from server" when 401 error returned from middleware (sometimes)I'm trying to figure out a problem where some (a few, not most) 401 error responses result in an uncHow do pass a 'blob' from frontend to backend?I'm having trouble doing this without converting an audio Blob to base64 and then decrypting it and fetch failed - error on npm start on productionI rebuild my next.js app with the npm run build command and I started it with npm start I get thisType return error when using mongoose.node - v16.15.1 npm I'm somewhat new to trpc. Using it with mongoose. Love it so far althought I douseContext won't infer type from tRPC clientI am calling tRPC endpoints from my app and it's working flawlessly. As you can see on first screensForward client headers with createTRPCProxyClient & Server-Side HelpersWith `createTRPCNext` i had the option to tap into the `context.req` object nextjs passed in, but I I have a websocket + REST project. Should I be using splitLink like this to combine WS and HTTP?I have a backend project that uses REST for queries/mutations and Websockets for subscriptions. I jNested procedures are separated by dots, is there a way to change that to `/`s instead ?currently it looks something like this `/api/trpc/post.byId`, is there a way to have it like `/api/tIs there a way to split a trpc api across multiple lambdas ?How do I go about splitting my TRPC api across multiple lambdas, such that each lambda would load thCan I alter the context in a procedure?Is there a proper way to do this? Mutating the `opts.ctx` directly seems wrong