0

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.

Mayank Pathela
  • 453
  • 1
  • 6
  • 15

1 Answers1

1

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:

  1. Let's Setup the Procedure and Initialize Router 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;

  1. Let's setup our Router, app/routes/api/root.ts: P.S. You can create different router files and add it here
import { 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;

  1. Create the Server Side API to consume the Routers, put it in the same path as mentioned in client side file, in this example path will be- 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.

Mayank Pathela
  • 453
  • 1
  • 6
  • 15