Custom Bodies
Model non-JSON request and response bodies.
Default JSON object request bodies are flattened into handler and client input.
Use customBody() when the body should be treated as one whole value instead or when the body is not JSON.
import { customBody } from "@rest-rpc/core";
request: {
params: {
id: z.string(),
},
body: customBody({
contentType: "image/png",
schema: z.instanceof(Uint8Array),
}),
}
The handler and client shape is:
{
id: string;
body: Uint8Array;
}
Request Bodies
export const api = router({
images: {
upload: {
method: "PUT",
path: "/images/:id",
request: {
params: {
id: z.string(),
},
body: customBody({
contentType: "image/png",
schema: z.instanceof(Uint8Array),
}),
},
responses: {
204: noBody(),
},
},
},
});
upload({ id, body }) {
saveImage(id, body);
return undefined;
}
await client.images.upload.fetch({
id: "image_1",
body: new Uint8Array(await file.arrayBuffer()),
});
Response Bodies
Use customBody() in responses when the response body has a specific content
type.
download: {
method: "GET",
path: "/images/:id",
request: {
params: {
id: z.string(),
},
},
responses: {
200: customBody({
contentType: "image/png",
schema: z.instanceof(Uint8Array),
}),
404: z.object({
code: z.literal("IMAGE_NOT_FOUND"),
}),
},
}
On the client, a custom response body exposes the raw Response object.
const response = await client.images.download.fetch({
id: "image_1",
});
const bytes = await response.arrayBuffer();
Body Parsing
Server adapters do not infer your intended body parsing.
For Express, choose middleware before route execution.
import { isCustomBody } from "@rest-rpc/core";
import { createRouteMatcher } from "@rest-rpc/express";
const matchContractRoute = createRouteMatcher(api);
const jsonParser = express.json();
const pngParser = express.raw({ type: "image/png" });
app.use((req, res, next) => {
const match = matchContractRoute({
method: req.method,
path: req.path,
});
const body = match?.route.request?.body;
if (isCustomBody(body) && body.contentType === "image/png") {
return pngParser(req, res, next);
}
return jsonParser(req, res, next);
});
For Hono, use parseBody.
registerRoutes(app, routes, {
parseBody: async ({ c, body }) => {
if (isCustomBody(body)) {
return c.req.arrayBuffer();
}
return c.req.json();
},
});