Skip to main content
Version: 11.x

非 JSON 内容类型

除了 JSON 序列化数据外,tRPC 还可以使用 FormData、File 和其他二进制类型作为过程输入。

¥In addition to JSON-serializable data, tRPC can use FormData, File, and other Binary types as procedure inputs

客户端设置

¥Client Setup

信息

虽然 tRPC 原生支持多种非 JSON 序列化类型,但你的客户端可能需要根据你的设置进行一些链接配置才能支持这些类型。

¥While tRPC natively supports several non-json serializable types, your client may need a little link configuration to support them depending on your setup.

httpLink 开箱即用地支持非 JSON 内容类型,如果你只使用此功能,则你现有的设置应该可以立即生效。

¥httpLink supports non-json content types out the box, if you're only using this then your existing setup should work immediately

ts
import { httpLink } from '@trpc/client';
trpc.createClient({
links: [
httpLink({
url: 'http://localhost:2022',
}),
],
});
ts
import { httpLink } from '@trpc/client';
trpc.createClient({
links: [
httpLink({
url: 'http://localhost:2022',
}),
],
});

但是,并非所有链接都支持这些新的内容类型。如果你使用 httpBatchLinkhttpBatchStreamLink,则需要包含 splitLink 并根据内容检查要使用的链接。

¥However, not all links support these new content types, if you're using httpBatchLink or httpBatchStreamLink you will need to include a splitLink and check which link to use depending on the content

ts
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
trpc.createClient({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
}),
],
});
ts
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
trpc.createClient({
links: [
splitLink({
condition: (op) => isNonJsonSerializable(op.input),
true: httpLink({
url,
}),
false: httpBatchLink({
url,
}),
}),
],
});

如果你在 tRPC 服务器中使用 transformer,TypeScript 要求你的 tRPC 客户端链接也定义 transformer。以此示例为基础:

¥If you are using transformer in your tRPC server, typescript requires that your tRPC client link defines transformer as well.\ Use this example as base:

ts
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
trpc.createClient({
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: superjson.deserialize, // or your other transformer
},
}),
false: httpBatchLink({
url,
transformers: superjson, // or your other transformer
}),
}),
],
});
ts
import {
httpBatchLink,
httpLink,
isNonJsonSerializable,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
trpc.createClient({
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: superjson.deserialize, // or your other transformer
},
}),
false: httpBatchLink({
url,
transformers: superjson, // or your other transformer
}),
}),
],
});

服务器使用

¥Server Usage

信息

当 tRPC 处理请求时,它会根据请求的 Content-Type 标头解析请求正文。如果你遇到类似 Failed to parse body as XXX 的错误,请确保你的服务器(例如 Express、Next.js)在 tRPC 处理请求正文之前没有解析它。

¥When a request is handled by tRPC, it takes care of parsing the request body based on the Content-Type header of the request.\ If you encounter errors like Failed to parse body as XXX, make sure that your server (e.g., Express, Next.js) isn't parsing the request body before tRPC handles it.

ts
// Example in express
// incorrect
const app = express();
app.use(express.json()); // this try to parse body before tRPC.
app.post('/express/hello', (req,res) => {/* ... */ }); // normal express route handler
app.use('/trpc', trpcExpress.createExpressMiddleware({ /* ... */}))// tRPC fails to parse body
// correct
const app = express();
app.use('/express', express.json()); // do it only in "/express/*" path
app.post('/express/hello', (req,res) => {/* ... */ });
app.use('/trpc', trpcExpress.createExpressMiddleware({ /* ... */}))// tRPC can parse body
ts
// Example in express
// incorrect
const app = express();
app.use(express.json()); // this try to parse body before tRPC.
app.post('/express/hello', (req,res) => {/* ... */ }); // normal express route handler
app.use('/trpc', trpcExpress.createExpressMiddleware({ /* ... */}))// tRPC fails to parse body
// correct
const app = express();
app.use('/express', express.json()); // do it only in "/express/*" path
app.post('/express/hello', (req,res) => {/* ... */ });
app.use('/trpc', trpcExpress.createExpressMiddleware({ /* ... */}))// tRPC can parse body

FormData 输入

¥FormData Input

FormData 是原生支持的,对于更高级的用法,你还可以将其与 zod-form-data 之类的库结合使用,以类型安全的方式验证输入。

¥FormData is natively supported, and for more advanced usage you could also combine this with a library like zod-form-data to validate inputs in a type-safe way.

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')}`,
};
}),
});

更高级的代码示例,你可以查看我们的 示例项目在此

¥For a more advanced code sample you can see our example project here

File 和其他二进制类型输入

¥File and other Binary Type Inputs

tRPC 将许多八位字节内容类型转换为 ReadableStream,可在过程中使用。目前支持的接口有 BlobUint8ArrayFile

¥tRPC converts many octet content types to a ReadableStream which can be consumed in a procedure. Currently these are Blob Uint8Array and File.

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,
};
}),
});