tRPC documentation has step by step guide for popular SSR framework like NextJS, but most of the example uses NextJS adapters and middle-ware. There's no documentation for setting it up with Remix which is yet another popular SSR React framework.
Client Side: This will be a common set-up for all frameworks, here I have used superjson as transformer for serialization-
// app/utils/api.ts
import {
createTRPCProxyClient,
httpLink,
loggerLink,
} from '@trpc/client';
import { type inferRouterInputs, type inferRouterOutputs } from '@trpc/server';
import superjson from 'superjson';
import { type AppRouter } from '~/routes/api/root';
const getBaseUrl = () => {
if (typeof window !== 'undefined') return ''; // browser should use relative url
// Change it to point to you SSR base URL
return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
export const client = createTRPCProxyClient<AppRouter>({
transformer: superjson,
links: [
loggerLink({
enabled: (opts) =>
process.env.NODE_ENV === 'development' ||
(opts.direction === 'down' && opts.result instanceof Error),
}),
httpLink({
url: `${getBaseUrl()}/api/trpc`, // We need to setup Server Side API to point to this
}),
],
});
export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
Server Side:
app/routes/api/trpc.ts
, here I am using Zod to intercept the request for type checking, you add Authentication layer also here:import { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch';
import { prisma } from '~/utils/prisma.server';
type CreateContextOptions = Record<string, never>;
export const createTRPCContext = ({
req,
resHeaders,
}: FetchCreateContextFnOptions) => {
return {
prisma,
};
};
import { inferAsyncReturnType, initTRPC } from '@trpc/server';
import superjson from 'superjson';
import { ZodError } from 'zod';
export type Context = inferAsyncReturnType<typeof createTRPCContext>;
const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
},
};
},
allowOutsideOfServer: true,
});
export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure;
app/routes/api/root.ts
:
P.S. You can create different router files and add it hereimport { createTRPCRouter } from '~/routes/api/trpc';
export const appRouter = createTRPCRouter({
testDelete: publicProcedure.input(z.object({
id: XXXXXX
})).mutation(async ({ctx, input}) => {
return ctx.primsa.test.delete({
where: { id: input.id }
})
}),
});
// export type definition of API
export type AppRouter = typeof appRouter;
app/routes/api/trpc/$.ts
:import { createTRPCContext } from '~/routes/api/trpc';
import { appRouter } from '~/routes/api/root';
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
import { ActionArgs, LoaderArgs } from '@remix-run/node';
// Both Action and Loaders will point to tRPC Router
export const loader = async (args: LoaderArgs) => {
return handleRequest(args);
};
export const action = async (args: ActionArgs) => {
return handleRequest(args);
};
function handleRequest(args: LoaderArgs | ActionArgs) {
return fetchRequestHandler({
endpoint: '/api/trpc',
req: args.request,
router: appRouter,
createContext: createTRPCContext,
});
}
The tRPC Setup is now complete, to consume it import the client in any Component and make the call:
......
import { client } from '~/utils/api';
......
......
function onDelete(id) {
......
await client.testDelete.mutate({ id: id })
......
}
return <div>.....</div>
You can add more routes in appRouter accordingly.