Finn
Finn9mo 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
Finn9mo 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
BillyBob8mo ago
@Finn Did you have to fix the content-length and type headers with this solution ?
Finn
Finn8mo 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
BillyBob8mo 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
Finn8mo 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
BillyBob8mo 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
Finn8mo ago
yes
BillyBob
BillyBob8mo 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
Finn8mo 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.