HTTP RPC Specification

Methods <-> Type mapping

HTTP MethodMappingNotes
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 the pathname
  • Input parameters are sent as a a query parameter called input which has the shape Record<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.ts

  1. export const appRouter = trpc
  2. .router<Context>()
  3. .query('postById', {
  4. input: String,
  5. async resolve({ input, ctx }) {
  6. const post = await ctx.post.findUnique({
  7. where: { id: input },
  8. });
  9. return post;
  10. },
  11. })
  12. .query('relatedPosts', {
  13. input: String,
  14. async resolve({ ctx, input }) {
  15. const posts = await ctx.findRelatedPostsById(input)
  16. return posts;
  17. },
  18. })

.. And two queries defined like this in a React component:

MyComponent.tsx

  1. export function MyComponent() {
  2. const post1 = trpc.useQuery(['postById', '1'])
  3. const relatedPosts = trpc.useQuery(['relatedPosts', '1'])
  4. return (
  5. <pre>{
  6. JSON.stringify(
  7. {
  8. post1: post1.data ?? null,
  9. relatedPosts: relatedPosts.data ?? null,
  10. },
  11. null,
  12. 4,
  13. )
  14. }</pre>
  15. )
  16. }

The above would result in exactly 1 HTTP call with this data:

Location propertyValue
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:

  1. encodeURIComponent(
  2. JSON.stringify({
  3. 0: '1', // <-- input for `postById`
  4. 1: '1', // <-- input for `relatedPosts`
  5. })
  6. )

Batching Example Response

Example output from server

  1. [
  2. // result for `postById`
  3. {
  4. "id": null,
  5. "result": {
  6. "type": "data",
  7. "data": {
  8. "id": "1",
  9. "title": "Hello tRPC",
  10. "body": "..."
  11. // ...
  12. }
  13. }
  14. },
  15. // result for `relatedPosts`
  16. {
  17. "id": null,
  18. "result": {
  19. "type": "data",
  20. "data": [
  21. /* ... */
  22. ]
  23. }
  24. }
  25. ]

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

  1. {
  2. "id": null,
  3. "result": {
  4. "type": "data",
  5. "data": {
  6. "id": "1",
  7. "title": "Hello tRPC",
  8. "body": "..."
  9. }
  10. }
  11. }
  1. {
  2. id: null;
  3. result: {
  4. type: 'data';
  5. data: TOutput; // output from procedure
  6. };
  7. }

Error Response

Example JSON Response

  1. [
  2. {
  3. "id": null,
  4. "error": {
  5. "json": {
  6. "message": "Something went wrong",
  7. "code": -32600, // JSON-RPC 2.0 code
  8. "data": { // Extra, customizable, meta data
  9. "code": "INTERNAL_SERVER_ERROR",
  10. "httpStatus": 500,
  11. "stack": "...",
  12. "path": "post.add"
  13. }
  14. }
  15. }
  16. }
  17. ]
  • 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

  1. PARSE_ERROR: 400,
  2. BAD_REQUEST: 400,
  3. NOT_FOUND: 404,
  4. INTERNAL_SERVER_ERROR: 500,
  5. UNAUTHORIZED: 401,
  6. FORBIDDEN: 403,
  7. TIMEOUT: 408,
  8. CONFLICT: 409,
  9. CLIENT_CLOSED_REQUEST: 499,
  10. PRECONDITION_FAILED: 412,
  11. PAYLOAD_TOO_LARGE: 413,
  12. METHOD_NOT_SUPPORTED: 405,

Error Codes <-> JSON-RPC 2.0 Error Codes

Available codes & JSON-RPC code

  1. /**
  2. * JSON-RPC 2.0 Error codes
  3. *
  4. * `-32000` to `-32099` are reserved for implementation-defined server-errors.
  5. * For tRPC we're copying the last digits of HTTP 4XX errors.
  6. */
  7. export const TRPC_ERROR_CODES_BY_KEY = {
  8. /**
  9. * Invalid JSON was received by the server.
  10. * An error occurred on the server while parsing the JSON text.
  11. */
  12. PARSE_ERROR: -32700,
  13. /**
  14. * The JSON sent is not a valid Request object.
  15. */
  16. BAD_REQUEST: -32600, // 400
  17. /**
  18. * Internal JSON-RPC error.
  19. */
  20. INTERNAL_SERVER_ERROR: -32603,
  21. // Implementation specific errors
  22. UNAUTHORIZED: -32001, // 401
  23. FORBIDDEN: -32003, // 403
  24. NOT_FOUND: -32004, // 404
  25. METHOD_NOT_SUPPORTED: -32005, // 405
  26. TIMEOUT: -32008, // 408
  27. CONFLICT: -32009, // 409
  28. PRECONDITION_FAILED: -32012, // 412
  29. PAYLOAD_TOO_LARGE: -32013, // 413
  30. CLIENT_CLOSED_REQUEST: -32099, // 499
  31. } as const;

Dig deeper

You can read more details by drilling into the TypeScript definitions in