tRPC over WebSocket with Next.js and NextAuth

Hi. I'm trying to set up Next 13 with tRPC over a WebSocket and NextAuth. I got it mostly working with:
// route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from '@/server/context';
import { appRouter } from '@/server/routers/app';

const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
batching: {
enabled: true,
},
});

export { handler as GET, handler as POST };
// route.ts
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { createContext } from '@/server/context';
import { appRouter } from '@/server/routers/app';

const handler = (req: Request) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext,
batching: {
enabled: true,
},
});

export { handler as GET, handler as POST };
// context.ts
import * as trpc from '@trpc/server';
import * as trpcNext from '@trpc/server/adapters/fetch';
import { NodeHTTPCreateContextFnOptions } from '@trpc/server/adapters/node-http';
import { IncomingMessage } from 'node:http';
import ws from 'ws';
import { getSession } from 'next-auth/react';

export async function createContext(
opts:
| NodeHTTPCreateContextFnOptions<IncomingMessage, ws>
| trpcNext.FetchCreateContextFnOptions
) {
const req = opts?.req;

const session = req && (await getSession({ req }));

return {
req,
session,
};
}

export type Context = trpc.inferAsyncReturnType<typeof createContext>;
// context.ts
import * as trpc from '@trpc/server';
import * as trpcNext from '@trpc/server/adapters/fetch';
import { NodeHTTPCreateContextFnOptions } from '@trpc/server/adapters/node-http';
import { IncomingMessage } from 'node:http';
import ws from 'ws';
import { getSession } from 'next-auth/react';

export async function createContext(
opts:
| NodeHTTPCreateContextFnOptions<IncomingMessage, ws>
| trpcNext.FetchCreateContextFnOptions
) {
const req = opts?.req;

const session = req && (await getSession({ req }));

return {
req,
session,
};
}

export type Context = trpc.inferAsyncReturnType<typeof createContext>;
Solution:
My guess is that next-auth tries to use
import { headers } from 'next/headers';
import { headers } from 'next/headers';
but when you are not using Next, in the case of your ws server that import does nothing. You might want to take a look at examples-next-prisma-websocket-starter, if you are looking for a method to implement tRPC + Next + Next Auth + Websockets....
TS
Tamás Soós174d ago
and a provider component
TS
Tamás Soós174d ago
TS
Tamás Soós174d ago
My issue is with the createContext call. The opts type isn't quite right. getSession is giving me:
Type 'IncomingMessage | Request' is not assignable to type '(Partial<IncomingMessage> & { body?: any; }) | undefined'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage> & { body?: any; }'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage>'.
Types of property 'headers' are incompatible.
Type 'Headers' is not assignable to type 'IncomingHttpHeaders'.
Index signature for type 'string' is missing in type 'Headers'.ts(2322)
(property) CtxOrReq.req?: (Partial<IncomingMessage> & {
body?: any;
}) | undefined
Type 'IncomingMessage | Request' is not assignable to type '(Partial<IncomingMessage> & { body?: any; }) | undefined'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage> & { body?: any; }'.
Type 'Request' is not assignable to type 'Partial<IncomingMessage>'.
Types of property 'headers' are incompatible.
Type 'Headers' is not assignable to type 'IncomingHttpHeaders'.
Index signature for type 'string' is missing in type 'Headers'.ts(2322)
(property) CtxOrReq.req?: (Partial<IncomingMessage> & {
body?: any;
}) | undefined
even though it happens to be working for now. Btw getSession creates infinite reconnects on the ws client with any other inputs and getServerSession does the same except I couldn't find any arguments that work at all. Adding a try catch in there slows down the reconnect attempts to a few a seconds vs the original that reconnected as fast as it could. getServerSession throws
Error: Invariant: headers() expects to have requestAsyncStorage, none available.
at headers (/Users/tamas/Projects/oddp/node_modules/next/dist/client/components/headers.js:42:15)
at getServerSession (/Users/tamas/Projects/oddp/node_modules/next-auth/next/index.js:139:35)
at createContext (/Users/tamas/Projects/oddp/src/server/context.ts:22:21)
at WebSocketServer.<anonymous> (/Users/tamas/Projects/oddp/node_modules/@trpc/server/dist/adapters/ws.js:82:28)
at WebSocketServer.emit (node:events:526:35)
at WebSocketServer.emit (node:domain:488:12)
at WebSocketServer.completeUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:427:5)
at WebSocketServer.handleUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:336:10)
at Server.upgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:112:16)
at Server.emit (node:events:514:28)
Error: Invariant: headers() expects to have requestAsyncStorage, none available.
at headers (/Users/tamas/Projects/oddp/node_modules/next/dist/client/components/headers.js:42:15)
at getServerSession (/Users/tamas/Projects/oddp/node_modules/next-auth/next/index.js:139:35)
at createContext (/Users/tamas/Projects/oddp/src/server/context.ts:22:21)
at WebSocketServer.<anonymous> (/Users/tamas/Projects/oddp/node_modules/@trpc/server/dist/adapters/ws.js:82:28)
at WebSocketServer.emit (node:events:526:35)
at WebSocketServer.emit (node:domain:488:12)
at WebSocketServer.completeUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:427:5)
at WebSocketServer.handleUpgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:336:10)
at Server.upgrade (/Users/tamas/Projects/oddp/node_modules/ws/lib/websocket-server.js:112:16)
at Server.emit (node:events:514:28)
Solution
B
BeBoRE172d ago
My guess is that next-auth tries to use
import { headers } from 'next/headers';
import { headers } from 'next/headers';
but when you are not using Next, in the case of your ws server that import does nothing. You might want to take a look at examples-next-prisma-websocket-starter, if you are looking for a method to implement tRPC + Next + Next Auth + Websockets. I opted to use LuciaJS, because it's a little bit more versatile and less restrictive, but it's a little bit more complex and requires more boilerplate code. https://github.com/BeBoRE/ei-noah-bot
GitHub
GitHub - BeBoRE/ei-noah-bot: De officiële Discord Bot voor de Sweat...
De officiële Discord Bot voor de Sweaty GG Chat. Contribute to BeBoRE/ei-noah-bot development by creating an account on GitHub.
TS
Tamás Soós172d ago
Yes, that's a nice starter. The type issue in their context file I was able to solve by only defining the type there, and using getServerSession in the api route vs getSession in the ws server. Still it would be nice to use getServerSession in both places. I'll check out Lucia. The docs are nice. Thanks. Since in the end this is more of a NextAuth issue, I asked them directly on github. I'll close this post.
More Posts
Server Side Calls in tRPC not working (Next 13, App Router)Where do I make the helper?Project structure ?I greatly appreciate for your insight on this, especially if you're an experienced fullstack dev: BAccess procedures with same names and same parameters, but in different routers, genericallyMy trpc structure looks something like this: ``` trpc - router1 - routerX - proc1 - rDisable trpc route cahce?```ts import { protectedProcedure, router } from "@/lib/trpc/server/trpc"; import { TRPCError } fromHow do you set headers or cookies in procedure ?Having trouble figuring out how to set headers within a procedure in Next 13.Why use unstable_httpBatchStreamLink in React server components?When using create-t3-app I noticed that they were using `unstable_httpBatchStreamLink` in their `TRPUnsafe return of an `any` typedI am using TRPC with Prisma. I can't seem to get rid of the TypeScript ESlint error. ``` import { zVanilla Client AuthenticationHow to authenticate using the vanilla client? I want to consume this route as the authenticated userGeneric Zod as Input TypeI'm not positive if this is a TRPC question, typescript in general, or failure to understand Zod in Incompatible Router TypesI am working on a trpc implementation for sveltekit. I attached a screenshot of the createContext nextjs app router, "fs", "os", "zlib-sync" and trpc experimental edge RouterEnvironement: Turborepo + pnpm What's wrong: when I tried to build nextjs, the error is like this TS4111: Property 'error' comes from an index signature, so it must be accessed with ['error'].I am trying to use trpc with AnalogJs, an Angular meta framework and Nx. We use the @nx/vite builderTypeError: Cannot read properties of null (reading '_def')As the title says, I get the following: TypeError: Cannot read properties of null (reading '_def') I wanna add a localStorage persister, but I'm getting errors for hydration:I wanna add a localStorage persister, but I'm getting errors for hydration: ```js const persister =error route always getting 500 from trpc error? (next13/approuter)I've got this condition for throwing in my trpc procedure: ```ts if (!userClerkProfile) { force-cache planetscale errortrying to use the trpc API from RSCs fails and gives the following error ```log result: { data:What is the pattern for unsubscribing from a subscription?Hi there! I'd like to prevent unnecessary connections to a websocket server across multiple renders.How to add a short delay between requestsEnvironment: nextjs 13, node 18, npm, trpc 10.9.0 I'm wondering if it's possible to add a short del