trpc-rest

Error Handling

How errors are formatted and returned in REST responses.

The handler uses tRPC's error system. All errors flow through your errorFormatter and are returned as JSON responses with appropriate HTTP status codes.

Error response format

{
  "message": "Input validation failed",
  "code": "BAD_REQUEST",
  "issues": [
    { "message": "Expected string, received number" }
  ]
}

The issues field is only present for input validation errors (when the Zod parse fails).

HTTP status codes

tRPC CodeHTTP Status
BAD_REQUEST400
UNAUTHORIZED401
FORBIDDEN403
NOT_FOUND404
TIMEOUT408
CONFLICT409
UNPROCESSABLE_CONTENT422
TOO_MANY_REQUESTS429
INTERNAL_SERVER_ERROR500
PARSE_ERROR400
UNSUPPORTED_MEDIA_TYPE415

Error formatter integration

Your tRPC errorFormatter runs on all errors:

const t = initTRPC.meta<OpenApiMeta>().create({
  errorFormatter({ shape, error }) {
    return {
      ...shape,
      message: simplifyError(error),
      code: error.code,
    };
  },
});

Input validation errors

When Zod validation fails, tRPC throws a BAD_REQUEST error with the Zod error as the cause. The handler detects this and:

  1. Sets the message to "Input validation failed"
  2. Includes the Zod issues array in the response

Non-TRPCError handling

If a procedure throws a plain Error (not a TRPCError), the handler wraps it as an INTERNAL_SERVER_ERROR. The original error message is hidden from the client. Use onError to log the real error:

createOpenApiFetchHandler({
  // ...
  onError: ({ error }) => {
    if (error.code === 'INTERNAL_SERVER_ERROR') {
      console.error('Unhandled error:', error.cause);
    }
  },
});

TRPC_ERROR_CODE_HTTP_STATUS

The full error code to status code mapping is exported:

import { TRPC_ERROR_CODE_HTTP_STATUS } from 'trpc-rest';

const status = TRPC_ERROR_CODE_HTTP_STATUS['NOT_FOUND']; // 404

On this page