Finn
Finn6mo ago

Using tRPC in Next.js Middleware

Hello, I am quite new to tRPC so forgive me if I'm asking something quite obvious/dumb. I was introduced to tRPC by the t3-stack, that I discovered when I started with Next.js. Now onto the question. I am storing user session inside a database and want to validate a user that has a session_token cookie by its value. So, the value of the cookie needs to exist in the database. That way I can authenticate the request as well as associate a user. The problem I was facing is that I was having a hard time trying to access tRPC routes from the Next.js middleware. The t3 stack came with a pre-configured tRPC server and react client. Both of them do not work inside the Next.js middleware, because it runs on the Edge runtime. I have found some posts here that were asking a similar question, but none of them gave me an answer for my question. One of the posts helped solving the problem by suggesting to create a new client with a httpBatchLink that points to the api/trpc route. And that works the way it's suppose to. But right now, I export 3 api variables:
// src/trpc/react.tsx
export const api = createTRPCReact<AppRouter>();
// src/trpc/react.tsx
export const api = createTRPCReact<AppRouter>();
// src/trpc/server.ts
const createContext = cache(() => {
const heads = new Headers(headers());
// ...
});

export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
// ...
() =>
({ op }) =>
observable((observer) => {
createContext()
// ...
}),
],
});
// src/trpc/server.ts
const createContext = cache(() => {
const heads = new Headers(headers());
// ...
});

export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
// ...
() =>
({ op }) =>
observable((observer) => {
createContext()
// ...
}),
],
});
// src/trpc/edge.ts
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
fetch(url, options) {
return fetch(url, {
...options,
});
},
}),
],
});
// src/trpc/edge.ts
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
httpBatchLink({
url: `${getBaseUrl()}/api/trpc`,
fetch(url, options) {
return fetch(url, {
...options,
});
},
}),
],
});
Would you say, that what I've done is okay, or am I doing something completely unnecessary here?
10 Replies
Finn
Finn6mo ago
For reference, the whole template can be found here: https://github.com/t3-oss/create-t3-app/tree/main/cli/template/extras/src/trpc
BillyBob
BillyBob6mo ago
@Finn Did you have to fix the content-length and type headers with this solution ?
Finn
Finn6mo ago
Hi, I don't recall needing to change anything for it to work. But its been some time. I gave up trying to create my own authentication system and just started using lucia auth. Right now I use this api to validate requests in middleware.ts
import { createTRPCProxyClient, httpBatchLink, loggerLink } from "@trpc/client";

import { type AppRouter } from "@/server/api/root";
import { getUrl, transformer } from "./shared";

export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: op =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),

httpBatchLink({
url: getUrl(),
fetch(url, options) {
return fetch(url, {
...options,
});
},
}),
],
});
import { createTRPCProxyClient, httpBatchLink, loggerLink } from "@trpc/client";

import { type AppRouter } from "@/server/api/root";
import { getUrl, transformer } from "./shared";

export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: op =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),

httpBatchLink({
url: getUrl(),
fetch(url, options) {
return fetch(url, {
...options,
});
},
}),
],
});
But I still don't like that I have 3 different apis, for react, server and edge runtime. But I really don't have any clue as to what I could change for it to work otherwise.
BillyBob
BillyBob6mo ago
So you are not doing anything in Middleware right now? i had to do this:
headers(opts) {
// Any of the passed operations can override the headers
const headersOverride = opts.opList.find(
(op) => op.context?.headersOverride
)?.context.headersOverride as Record<string, string> | undefined



if (headersOverride) {
// console.log('headersOverride', headersOverride)
// console.log('headers', Object.fromEntries(headers()))
delete headersOverride['content-length']
delete headersOverride['content-type']
return headersOverride
}

const h = Object.fromEntries(headers())
delete h['content-length']
delete h['content-type']

return h
}
headers(opts) {
// Any of the passed operations can override the headers
const headersOverride = opts.opList.find(
(op) => op.context?.headersOverride
)?.context.headersOverride as Record<string, string> | undefined



if (headersOverride) {
// console.log('headersOverride', headersOverride)
// console.log('headers', Object.fromEntries(headers()))
delete headersOverride['content-length']
delete headersOverride['content-type']
return headersOverride
}

const h = Object.fromEntries(headers())
delete h['content-length']
delete h['content-type']

return h
}
Finn
Finn6mo ago
At the moment my middleware is just this:
export async function middleware(req: NextRequest): Promise<NextResponse> {
if (!isProtectedRoute(req.nextUrl.pathname)) return NextResponse.next();

const authCookie = cookies().get(SESSION_COOKIE_NAME);

if (!authCookie) {
return NextResponse.redirect(new URL("/signin", req.url));
}

const session = await api.session.validate.query({
sessionId: authCookie.value,
});

if (!session) {
return NextResponse.redirect(new URL("/signin", req.url));
}

return NextResponse.next();
}
export async function middleware(req: NextRequest): Promise<NextResponse> {
if (!isProtectedRoute(req.nextUrl.pathname)) return NextResponse.next();

const authCookie = cookies().get(SESSION_COOKIE_NAME);

if (!authCookie) {
return NextResponse.redirect(new URL("/signin", req.url));
}

const session = await api.session.validate.query({
sessionId: authCookie.value,
});

if (!session) {
return NextResponse.redirect(new URL("/signin", req.url));
}

return NextResponse.next();
}
I think that you should better create a new thread for that, if you're having problems with your code. I am very much not qualified to give any advice on tRPC whatsoever. I just took a quick look at the create-t3-app template and went on with it. The configuration of tRPC is a myth to me. Sorry mate.
BillyBob
BillyBob6mo ago
interesting that this works
const session = await api.session.validate.query({
sessionId: authCookie.value,
});
const session = await api.session.validate.query({
sessionId: authCookie.value,
});
to call tRPC in the nextJS middleware was why I needed to change the headers are you on 14.1 ?
Finn
Finn6mo ago
yes
BillyBob
BillyBob6mo ago
me too. i wonder if they changed something. because i am sure this didnt work before,
headers() {
return {
cookie: cookies().toString(),
}
},
headers() {
return {
cookie: cookies().toString(),
}
},
Because now all i need is this ^
Finn
Finn6mo ago
Well, the cookies are missing in the request to the API for me as well - if that's what you mean. That's why I just read the cookie in the middleware and pass it into the api call, rather than reading the cookie within the procedure itself.
More Posts
need help refreshing websocketWe currently are using tRPC w/ react and websockets. We’re using the URL of the websocket as the autrouter is crashing when in separate fileWhen i use router merging and have e.g users router in separate file, i import router from trpc.ts aHow do I pass a Generic to a trpc query procedure?I want to to something like this: ```ts type AppIdsWithConfig = typeof kodixCareAppId | typeof calenIs there something to be done about trpc errors and solidjs/seroval?Basically if you `throw error` in trpc route, solidjs seroval fails to serialize it during SSR. I doHow can i createCaller from a NextJs App Router if my server uses express tRPC adapter?I'm using the express adapter for the server side of tRPC, and the client is a NextJS AppRouter app.Is there a way to refetch a query with new parameters?Hi I'm using tRPC in a Next.js app and I have a button that a user can click to get the latest dataIs there any benefit to putting the db connection in the context versus having it as an global var?Can I do this? ```ts export const challengesRouter = router({ getChallengeById: privateProcedure Controller is already closed crashIm using trpc with nextjs 14 app router and started to see that my app crashes due to such error. DiThis page needs to bail out of prerendering at this point because it used revalidate: 0I've been testing out partial prerendering with next using tRPC but having some issues. Everytime How to infer query types on client?```ts export const tenantRouter = router({ getTenants: procedure.query(() => { return { hello: