niko
niko3w ago

Building CLI tool for trpc for type generation

I'm building trpc types cli tool for my use case. i use nextjs for api and i want to implement same functionality in another nextjs app without copy pasting whole api folder to that one, i already made type generation tool just testing around if it will work this is what main nextjs api /api/trpc/[trpc]/route.ts code looks like, im allowing to bypass cors issue when making request from another nextjs app in my case localhost:3000 is main and localhost:3001 is second
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";

import { env } from "@/env";
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";

const corsHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3001",
"Access-Control-Request-Method": "*",
"Access-Control-Allow-Methods": "OPTIONS, GET, POST",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Credentials": "true",
};

const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = async (req: NextRequest) => {
const response = await fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});

// Create a new response with CORS headers
const newResponse = new Response(response.body, response);
newResponse.headers.set("Content-Type", "application/json");

Object.entries(corsHeaders).forEach(([key, value]) => {
newResponse.headers.set(key, value);
});

return newResponse;
};

export { handler as GET, handler as POST, handler as OPTIONS };
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";

import { env } from "@/env";
import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";

const corsHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3001",
"Access-Control-Request-Method": "*",
"Access-Control-Allow-Methods": "OPTIONS, GET, POST",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Credentials": "true",
};

const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = async (req: NextRequest) => {
const response = await fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});

// Create a new response with CORS headers
const newResponse = new Response(response.body, response);
newResponse.headers.set("Content-Type", "application/json");

Object.entries(corsHeaders).forEach(([key, value]) => {
newResponse.headers.set(key, value);
});

return newResponse;
};

export { handler as GET, handler as POST, handler as OPTIONS };
2 Replies
niko
nikoOP3w ago
this is what second project react.tsx look like for making requests
"use client";

import { QueryClientProvider, type QueryClient } from "@tanstack/react-query";
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import { useState } from "react";
import SuperJSON from "superjson";

import { type AppRouter } from "@/schema/trpc-types"; // generated types
import { createQueryClient } from "./query-client";

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === "undefined") {
return createQueryClient();
}
return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();
export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: SuperJSON,
url: "http://localhost:3000/api/trpc", // main project api endpoint
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
headers.set("Content-Type", "application/json");
return headers;
},
fetch(url, options) {
return fetch(url, {
...options,
credentials: "include",
});
},
}),
],
}),
);

return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}
"use client";

import { QueryClientProvider, type QueryClient } from "@tanstack/react-query";
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
import { createTRPCReact } from "@trpc/react-query";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import { useState } from "react";
import SuperJSON from "superjson";

import { type AppRouter } from "@/schema/trpc-types"; // generated types
import { createQueryClient } from "./query-client";

let clientQueryClientSingleton: QueryClient | undefined = undefined;
const getQueryClient = () => {
if (typeof window === "undefined") {
return createQueryClient();
}
return (clientQueryClientSingleton ??= createQueryClient());
};

export const api = createTRPCReact<AppRouter>();
export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;

export function TRPCReactProvider(props: { children: React.ReactNode }) {
const queryClient = getQueryClient();

const [trpcClient] = useState(() =>
api.createClient({
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
transformer: SuperJSON,
url: "http://localhost:3000/api/trpc", // main project api endpoint
headers: () => {
const headers = new Headers();
headers.set("x-trpc-source", "nextjs-react");
headers.set("Content-Type", "application/json");
return headers;
},
fetch(url, options) {
return fetch(url, {
...options,
credentials: "include",
});
},
}),
],
}),
);

return (
<QueryClientProvider client={queryClient}>
<api.Provider client={trpcClient} queryClient={queryClient}>
{props.children}
</api.Provider>
</QueryClientProvider>
);
}
so for first time i had that issue it was sending OPTIONS request on trpc and i was getting cors errors for that and i tried to resolve by doing this, i though it would fix it but no
export { handler as GET, handler as POST, handler as OPTIONS };
export { handler as GET, handler as POST, handler as OPTIONS };
when im sending request from localhost:3001, this is what is loggin in locahost:3000 terminal
❌ tRPC failed on <no-path>: Missing content-type header
OPTIONS /api/trpc/getUsers?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%7D 415 in
❌ tRPC failed on <no-path>: Missing content-type header
OPTIONS /api/trpc/getUsers?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%2C%22meta%22%3A%7B%22values%22%3A%5B%22undefined%22%5D%7D%7D%7D 415 in
but i already put that header literally everywhere btw i use t3-app for both projects if i make axios.get request i get response with 200 status without any problems but if i use trpc useQuery it goes on CORS error
niko
nikoOP3w ago
this is the cors error
No description