英文:
How to Setup tRPC with Remix, Zod and Prisma
问题
tRPC文档提供了逐步指南,适用于流行的SSR框架,例如NextJS,但大多数示例使用了NextJS适配器和中间件。没有关于如何在Remix中设置的文档,而Remix是另一个流行的SSR React框架。
英文:
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.
答案1
得分: 1
Client Side:
这将是所有框架的通用设置,这里我使用superjson作为序列化的转换器-
// 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 ''; // 浏览器应使用相对 URL
// 将其更改为指向您的 SSR 基础 URL
return `http://localhost:${process.env.PORT ?? 3000}`; // 开发 SSR 应使用 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`, // 我们需要设置服务器端 API 指向这里
}),
],
});
export type RouterInputs = inferRouterInputs<AppRouter>;
export type RouterOutputs = inferRouterOutputs<AppRouter>;
Server Side:
- 让我们设置过程并初始化路由器
app/routes/api/trpc.ts
,在这里我使用 Zod 拦截请求进行类型检查,您还可以在此处添加身份验证层:
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. 您可以创建不同的路由文件并将其添加到此处:
import { createTRPCRouter } from '~/routes/api/trpc';
export const appRouter = createTRPCRouter({
testDelete: publicProcedure.input(z.object({
id: XXXXXX
})).mutation(async ({ ctx, input }) => {
return ctx.prisma.test.delete({
where: { id: input.id }
})
}),
});
// 导出 API 的类型定义
export type AppRouter = typeof appRouter;
- 创建服务器端 API 来使用路由器,将其放在与客户端侧文件中提到的相同路径中,例如在此示例中路径将是-
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';
// Action 和 Loaders 都将指向 tRPC 路由器
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,
});
}
tRPC 设置现在已经完成,要使用它,请在任何组件中导入客户端并进行调用:
......
import { client } from '~/utils/api';
......
......
function onDelete(id) {
......
await client.testDelete.mutate({ id: id })
......
}
return <div>.....</div>;
英文:
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:
- 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;
- 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;
- 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论