Poonda
Poonda2mo ago

Forward NextJs Request to TRPC Server

Hey everyone! I need some help with my project setup. Here's my current situation: My Setup: * I have a NextJS 14 project (which is both client-side and server-side) * I also have a separate ExpressJS tRPC server What I Want to Achieve: I want both the client-side and server-side of my NextJS app to communicate with the tRPC server. However, I have a specific requirement for the client-side: 1. The client-side of NextJS should first talk to a NextJS route handler 2. Then, this route handler should make the call to the tRPC server My Issue: I'm not sure how to set this up correctly. I need help figuring out: 1. How to implement the NextJS route handler to act as a middleman 2. Best practices for this kind of setup? Has anyone done something similar or have any ideas on how to approach this? Any tips, code snippets, or resources would be greatly appreciated!
No description
7 Replies
BeastFox
BeastFox2mo ago
Hey, i’ll give my two cents but others might have better ideas. Any chance you could give some context to why you want to split the servers up firstly? Katt spoke about it somewhere a while ago, but if these are completely separate servers and not running off of the same service you’ll need to go over network for communication and look at the vanilla client https://trpc.io/docs/client/vanilla/setup. So you’d use that for your Next -> tRPC integration where Next calls your tRPC service. The tricky problem to solve would be type definitions, to be honest Im not quite sure how best you’d achieve this. My only recommendation is that you pass the tRPC type definitions to your NextJS client additionally and then use things like RouterOutput/RouterInput https://trpc.io/docs/client/vanilla/infer-types which would allow you to still keep your data typed whilst still communicating first via the Next Server (you’d have to setup endpoint management etc yourself). But I don’t think it’ll be possible to get the full type definitions if you aren’t communicating directly between tRPC. Unless you are basically mirroring endpoints between the next server and tRPC server it might be a bit tough to get the full feature set
Inferring Types | tRPC
It is often useful to access the types of your API within your clients. For this purpose, you are able to infer the types contained in your AppRouter.
Poonda
Poonda2mo ago
About the context to why I want to split the server. First of all all of the code sit in a Turbo monorepo. we have a react-native app, and a nextjs web-app that both need to talk to the TRPC server for the common logic. as of today i have my TRPC server on NextJs and React Native is talking to next server with TRPC. The problem we have is that each time i need to update some TRPC procedure I need to deploy and release NextJs as well, which is not favorable for us. About the type I don't think i would have problem, because when creating the trpcClient in NextJs, i use the AppRouter type from my internal package where i declare the appRouter and the procedures:
import { createTRPCReact } from "@trpc/react-query";
// ...Imports...
import { type AppRouter } from "@acme/trpc"; // Internal Package of Monorepo

export const trpcClient = createTRPCReact<AppRouter>();

export const queryClient = new QueryClient();

export default function TRPCProvider({ children }: { children: React.ReactNode }) {
const [client] = useState(() =>
trpcClient.createClient({
links: [
httpBatchLink({
url: `/api/trpc`,
}),
],
}),
);
return (
<trpcClient.Provider client={client} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</trpcClient.Provider>
);
}
import { createTRPCReact } from "@trpc/react-query";
// ...Imports...
import { type AppRouter } from "@acme/trpc"; // Internal Package of Monorepo

export const trpcClient = createTRPCReact<AppRouter>();

export const queryClient = new QueryClient();

export default function TRPCProvider({ children }: { children: React.ReactNode }) {
const [client] = useState(() =>
trpcClient.createClient({
links: [
httpBatchLink({
url: `/api/trpc`,
}),
],
}),
);
return (
<trpcClient.Provider client={client} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</trpcClient.Provider>
);
}
Poonda
Poonda2mo ago
if i understand the flow correctly, the image describe the flow. so i don't have type problem The Red part is what i'm trying to solve
No description
Poonda
Poonda2mo ago
in the same concept when i call trpc from my server component it work with the type infer:
import type { AppRouter } from "@acme/trpc"; // Internal Package

export const createTrpcServer = () => createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: `${CONFIG.TRPC_BACKEND}/trpc`,
headers: {
...Object.fromEntries(headers().entries()),
},
}),
]
});

export const trpcServer = createTrpcServer();
import type { AppRouter } from "@acme/trpc"; // Internal Package

export const createTrpcServer = () => createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: `${CONFIG.TRPC_BACKEND}/trpc`,
headers: {
...Object.fromEntries(headers().entries()),
},
}),
]
});

export const trpcServer = createTrpcServer();
No description
Poonda
Poonda2mo ago
for now i found doing this is working, but i really don't now when it will bite me back:
const handler = async (req: Request) => {
const body = await req.text();
const path = req.url.split("/api/trpc/")[1]; // taking the second half
const url = `${process.env.TRPC_SERVER}/trpc/${path}`

const headers = req.headers;
console.log("ROUTE", { body, url, headers });

const response = await fetch(url, {
method: req.method,
headers: req.headers,
// GET and HEAD can't have a body
body: req.method === "GET" ||
req.method === "HEAD" ?
null : body,
});
return new Response(response.body, {
status: response.status,
headers: response.headers,
})
};

export { handler as GET, handler as POST };
const handler = async (req: Request) => {
const body = await req.text();
const path = req.url.split("/api/trpc/")[1]; // taking the second half
const url = `${process.env.TRPC_SERVER}/trpc/${path}`

const headers = req.headers;
console.log("ROUTE", { body, url, headers });

const response = await fetch(url, {
method: req.method,
headers: req.headers,
// GET and HEAD can't have a body
body: req.method === "GET" ||
req.method === "HEAD" ?
null : body,
});
return new Response(response.body, {
status: response.status,
headers: response.headers,
})
};

export { handler as GET, handler as POST };
kapitanluffy
kapitanluffy2mo ago
@Poonda curious, why not talk to the trpc server directly? react-native <-> trpc next.js <-> trpc
Poonda
Poonda2mo ago
security, to not expose my TRPC server to the client side (I personally don't understand fully the why, but that's the task I got, I've tried to argue diffrently, but I'm not the Team Lead :D)