HTTP RPC Specification
Methods <-> Type mapping​
HTTP Method | Mapping | Notes |
---|---|---|
GET | .query() | Input JSON-stringified in query param. e.g. myQuery?input=${encodeURIComponent(JSON.stringify(input)) |
POST | .mutation() | Input as POST body. |
n/a | .subscription() | Subscriptions are not supported in HTTP transport |
Batching​
When batching, we combine all parallel procedure calls of the same type in one request using a data loader.
- The called procedures' names are combined by a comma (
,
) in thepathname
- Input parameters are sent as a query parameter called
input
which has the shapeRecord<number, unknown>
. - We also need to pass
batch=1
as a query parameter. - If the response has different statuses we send back
207 Multi-Status
(e.g. if one call errored and one succeeded)
Batching Example Request​
Given a router like this exposed at /api/trpc
:​
server/router.tstsx
export const appRouter = t.router({postById: t.procedure.input(String).query(async ({ input, ctx }) => {const post = await ctx.post.findUnique({where: { id: input },});return post;}),relatedPosts: t.procedure.input(String).query(async ({ input, ctx }) => {const posts = await ctx.findRelatedPostsById(input);return posts;}),});
server/router.tstsx
export const appRouter = t.router({postById: t.procedure.input(String).query(async ({ input, ctx }) => {const post = await ctx.post.findUnique({where: { id: input },});return post;}),relatedPosts: t.procedure.input(String).query(async ({ input, ctx }) => {const posts = await ctx.findRelatedPostsById(input);return posts;}),});
.. And two queries defined like this in a React component:​
MyComponent.tsxtsx
export function MyComponent() {const post1 = trpc.postById.useQuery('1');const relatedPosts = trpc.relatedPosts.useQuery('1');return (<pre>{JSON.stringify({post1: post1.data ?? null,relatedPosts: relatedPosts.data ?? null,},null,4,)}</pre>);}
MyComponent.tsxtsx
export function MyComponent() {const post1 = trpc.postById.useQuery('1');const relatedPosts = trpc.relatedPosts.useQuery('1');return (<pre>{JSON.stringify({post1: post1.data ?? null,relatedPosts: relatedPosts.data ?? null,},null,4,)}</pre>);}
The above would result in exactly 1 HTTP call with this data:​
Location property | Value |
---|---|
pathname | /api/trpc/postById,relatedPosts |
search | ?batch=1&input=%7B%220%22%3A%221%22%2C%221%22%3A%221%22%7D * |
*) input
in the above is the result of:
ts
encodeURIComponent(JSON.stringify({0: '1', // <-- input for `postById`1: '1', // <-- input for `relatedPosts`}),);
ts
encodeURIComponent(JSON.stringify({0: '1', // <-- input for `postById`1: '1', // <-- input for `relatedPosts`}),);
Batching Example Response​
Example output from server
json
[// result for `postById`{"result": {"data": {"id": "1","title": "Hello tRPC","body": "..."// ...}}},// result for `relatedPosts`{"result": {"data": [/* ... */]}}]
json
[// result for `postById`{"result": {"data": {"id": "1","title": "Hello tRPC","body": "..."// ...}}},// result for `relatedPosts`{"result": {"data": [/* ... */]}}]
HTTP Response Specification​
In order to have a specification that works regardless of the transport layer we try to conform to JSON-RPC 2.0 where possible.
Successful Response​
Example JSON Response
json
{"result": {"data": {"id": "1","title": "Hello tRPC","body": "..."}}}
json
{"result": {"data": {"id": "1","title": "Hello tRPC","body": "..."}}}
ts
{result: {data: TOutput; // output from procedure}}
ts
{result: {data: TOutput; // output from procedure}}
Error Response​
Example JSON Response
json
[{"error": {"json": {"message": "Something went wrong","code": -32600, // JSON-RPC 2.0 code"data": {// Extra, customizable, meta data"code": "INTERNAL_SERVER_ERROR","httpStatus": 500,"stack": "...","path": "post.add"}}}}]
json
[{"error": {"json": {"message": "Something went wrong","code": -32600, // JSON-RPC 2.0 code"data": {// Extra, customizable, meta data"code": "INTERNAL_SERVER_ERROR","httpStatus": 500,"stack": "...","path": "post.add"}}}}]
- When possible, we propagate HTTP status codes from the error thrown.
- If the response has different statuses we send back
207 Multi-Status
(e.g. if one call errored and one succeeded) - For more on errors and how customize them see Error Formatting.
Error Codes <-> HTTP Status​
ts
PARSE_ERROR: 400,BAD_REQUEST: 400,NOT_FOUND: 404,INTERNAL_SERVER_ERROR: 500,UNAUTHORIZED: 401,FORBIDDEN: 403,TIMEOUT: 408,CONFLICT: 409,CLIENT_CLOSED_REQUEST: 499,PRECONDITION_FAILED: 412,PAYLOAD_TOO_LARGE: 413,METHOD_NOT_SUPPORTED: 405,
ts
PARSE_ERROR: 400,BAD_REQUEST: 400,NOT_FOUND: 404,INTERNAL_SERVER_ERROR: 500,UNAUTHORIZED: 401,FORBIDDEN: 403,TIMEOUT: 408,CONFLICT: 409,CLIENT_CLOSED_REQUEST: 499,PRECONDITION_FAILED: 412,PAYLOAD_TOO_LARGE: 413,METHOD_NOT_SUPPORTED: 405,
Error Codes <-> JSON-RPC 2.0 Error Codes​
Available codes & JSON-RPC code
ts
/*** JSON-RPC 2.0 Error codes** `-32000` to `-32099` are reserved for implementation-defined server-errors.* For tRPC we're copying the last digits of HTTP 4XX errors.*/export const TRPC_ERROR_CODES_BY_KEY = {/*** Invalid JSON was received by the server.* An error occurred on the server while parsing the JSON text.*/PARSE_ERROR: -32700,/*** The JSON sent is not a valid Request object.*/BAD_REQUEST: -32600, // 400/*** Internal JSON-RPC error.*/INTERNAL_SERVER_ERROR: -32603,// Implementation specific errorsUNAUTHORIZED: -32001, // 401FORBIDDEN: -32003, // 403NOT_FOUND: -32004, // 404METHOD_NOT_SUPPORTED: -32005, // 405TIMEOUT: -32008, // 408CONFLICT: -32009, // 409PRECONDITION_FAILED: -32012, // 412PAYLOAD_TOO_LARGE: -32013, // 413CLIENT_CLOSED_REQUEST: -32099, // 499} as const;
ts
/*** JSON-RPC 2.0 Error codes** `-32000` to `-32099` are reserved for implementation-defined server-errors.* For tRPC we're copying the last digits of HTTP 4XX errors.*/export const TRPC_ERROR_CODES_BY_KEY = {/*** Invalid JSON was received by the server.* An error occurred on the server while parsing the JSON text.*/PARSE_ERROR: -32700,/*** The JSON sent is not a valid Request object.*/BAD_REQUEST: -32600, // 400/*** Internal JSON-RPC error.*/INTERNAL_SERVER_ERROR: -32603,// Implementation specific errorsUNAUTHORIZED: -32001, // 401FORBIDDEN: -32003, // 403NOT_FOUND: -32004, // 404METHOD_NOT_SUPPORTED: -32005, // 405TIMEOUT: -32008, // 408CONFLICT: -32009, // 409PRECONDITION_FAILED: -32012, // 412PAYLOAD_TOO_LARGE: -32013, // 413CLIENT_CLOSED_REQUEST: -32099, // 499} as const;
Dig deeper​
You can read more details by drilling into the TypeScript definitions in