跳至主内容
版本:11.x

内容类型

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

tRPC 支持多种内容类型作为过程输入:JSON 可序列化数据、FormData、File、Blob 及其他二进制类型。

JSON (默认)

默认情况下,tRPC 收发 JSON 可序列化数据。无需额外配置——任何可序列化为 JSON 的输入均可直接与所有链接(httpLinkhttpBatchLinkhttpBatchStreamLink)配合使用。

ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure.input(z.object({ name: z.string() })).query((opts) => {
return { greeting: `Hello ${opts.input.name}` };
}),
});
ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure.input(z.object({ name: z.string() })).query((opts) => {
return { greeting: `Hello ${opts.input.name}` };
}),
});

非 JSON 内容类型

除 JSON 外,tRPC 也可将 FormData、File 及其他二进制类型作为过程输入。

客户端配置

信息

虽然 tRPC 原生支持多种非 JSON 可序列化类型,但根据具体配置,客户端可能需要调整链接配置。

httpLink 原生支持非 JSON 内容类型——若仅使用此链接,现有配置可直接生效。

ts
import { createTRPCClient, httpLink } from '@trpc/client';
import type { AppRouter } from './server';
 
createTRPCClient<AppRouter>({
links: [
httpLink({
url: 'http://localhost:2022',
}),
],
});
ts
import { createTRPCClient, httpLink } from '@trpc/client';
import type { AppRouter } from './server';
 
createTRPCClient<AppRouter>({
links: [
httpLink({
url: 'http://localhost:2022',
}),
],
});

但并非所有链接都支持这些内容类型。若使用 httpBatchLinkhttpBatchStreamLink,需配置 splitLink 并根据内容类型路由请求。

ts
import {
createTRPCClient,
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import type { AppRouter } from './server';
 
const url = 'http://localhost:2022';
 
createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
}),
],
});
ts
import {
createTRPCClient,
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import type { AppRouter } from './server';
 
const url = 'http://localhost:2022';
 
createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
}),
],
});

若在 tRPC 服务端使用 transformer,TypeScript 要求客户端链接也定义 transformer。 参考以下基础示例:

ts
import {
createTRPCClient,
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server';
 
const url = 'http://localhost:2022';
 
createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
transformer: {
// request - convert data before sending to the tRPC server
serialize: (data) => data,
// response - convert the tRPC response before using it in client
deserialize: (data) => superjson.deserialize(data), // or your other transformer
},
}),
false: httpBatchLink({
url,
transformer: superjson, // or your other transformer
}),
}),
],
});
ts
import {
createTRPCClient,
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server';
 
const url = 'http://localhost:2022';
 
createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
transformer: {
// request - convert data before sending to the tRPC server
serialize: (data) => data,
// response - convert the tRPC response before using it in client
deserialize: (data) => superjson.deserialize(data), // or your other transformer
},
}),
false: httpBatchLink({
url,
transformer: superjson, // or your other transformer
}),
}),
],
});

服务端配置

信息

tRPC 处理请求时,会根据请求的 Content-Type 标头解析请求体。
若遇到类似 Failed to parse body as XXX 的错误,请确保服务端(如 Express、Next.js)未在 tRPC 前解析请求体。

ts
// Example in express
import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { appRouter } from './router';
 
// incorrect
const app1 = express();
app1.use(express.json()); // this tries to parse body before tRPC.
app1.post('/express/hello', (req, res) => { res.end(); }); // normal express route handler
app1.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter })); // tRPC fails to parse body
 
// correct
const app2 = express();
app2.use('/express', express.json()); // do it only in "/express/*" path
app2.post('/express/hello', (req, res) => { res.end(); });
app2.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter })); // tRPC can parse body
ts
// Example in express
import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { appRouter } from './router';
 
// incorrect
const app1 = express();
app1.use(express.json()); // this tries to parse body before tRPC.
app1.post('/express/hello', (req, res) => { res.end(); }); // normal express route handler
app1.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter })); // tRPC fails to parse body
 
// correct
const app2 = express();
app2.use('/express', express.json()); // do it only in "/express/*" path
app2.post('/express/hello', (req, res) => { res.end(); });
app2.use('/trpc', trpcExpress.createExpressMiddleware({ router: appRouter })); // tRPC can parse body

FormData 输入

FormData 已获原生支持。如需高级用法,可结合 zod-form-data 等库实现类型安全的输入验证

ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure.input(z.instanceof(FormData)).mutation((opts) => {
const data = opts.input;
const data: FormData
return {
greeting: `Hello ${data.get('name')}`,
};
}),
});
ts
import { z } from 'zod';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
hello: publicProcedure.input(z.instanceof(FormData)).mutation((opts) => {
const data = opts.input;
const data: FormData
return {
greeting: `Hello ${data.get('name')}`,
};
}),
});

高级代码示例请参见此处示例项目

File 及其他二进制类型输入

tRPC 会将多种八位元内容类型转换为 ReadableStream 供过程使用,当前支持类型包括 BlobUint8ArrayFile

ts
import { octetInputParser } from '@trpc/server/http';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
upload: publicProcedure.input(octetInputParser).mutation((opts) => {
const data = opts.input;
const data: ReadableStream<any>
return {
valid: true,
};
}),
});
ts
import { octetInputParser } from '@trpc/server/http';
 
export const t = initTRPC.create();
const publicProcedure = t.procedure;
 
export const appRouter = t.router({
upload: publicProcedure.input(octetInputParser).mutation((opts) => {
const data = opts.input;
const data: ReadableStream<any>
return {
valid: true,
};
}),
});