Skip to content

Error Handling in oRPC

oRPC offers a robust error handling system. You can either throw standard JavaScript errors or, preferably, use the specialized ORPCError class to utilize oRPC features.

There are two primary approaches:

  • Normal Approach: Throw errors directly (using ORPCError is recommended for clarity).
  • Type‑Safe Approach: Predefine error types so that clients can infer and handle errors in a type‑safe manner.

WARNING

The ORPCError.data property is sent to the client. Avoid including sensitive information.

Normal Approach

In the traditional approach you may throw any JavaScript error. However, using the ORPCError class improves consistency and ensures that error codes and optional data are handled appropriately.

Key Points:

  • The first argument is the error code.
  • You may optionally include a message, additional error data, or any standard error options.
ts
const rateLimit = os.middleware(async ({ next }) => {
  throw new ORPCError('RATE_LIMITED', {
    message: 'You are being rate limited',
    data: { retryAfter: 60 }
  })
  return next()
})

const example = os
  .use(rateLimit)
  .handler(async ({ input }) => {
    throw new ORPCError('NOT_FOUND')
    throw new Error('Something went wrong') // <-- will be converted to INTERNAL_SERVER_ERROR
  })

DANGER

Do not pass sensitive data in the ORPCError.data field.

Type‑Safe Error Handling

For a fully type‑safe error management experience, define your error types using the .errors method. This lets the client infer the error’s structure and handle it accordingly. You can use any Standard Schema library to validate error data.

ts
const 
base
=
os
.
errors
({ // <-- common errors
RATE_LIMITED
: {
data
:
z
.
object
({
retryAfter
:
z
.
number
(),
}), },
UNAUTHORIZED
: {},
}) const
rateLimit
=
base
.
middleware
(async ({
next
,
errors
}) => {
throw
errors
.
RATE_LIMITED
({
message
: 'You are being rate limited',
data
: {
retryAfter
: 60 }
}) return
next
()
}) const
example
=
base
.
use
(
rateLimit
)
.
errors
({
NOT_FOUND
: {
message
: 'The resource was not found', // <-- default message
}, }) .
handler
(async ({
input
,
errors
}) => {
throw
errors
.
NOT_FOUND
()
})

DANGER

Again, avoid including any sensitive data in the error data since it will be exposed to the client.

Learn more about Client Error Handling.

Combining Both Approaches

You can combine both strategies seamlessly. When you throw an ORPCError instance, if the code and data match with the errors defined in the .errors method, oRPC will treat it exactly as if you had thrown errors.[code] using the type‑safe approach.

ts
const base = os.errors({ // <-- common errors
  RATE_LIMITED: {
    data: z.object({
      retryAfter: z.number().int().min(1).default(1),
    }),
  },
  UNAUTHORIZED: {},
})

const rateLimit = base.middleware(async ({ next, errors }) => {
  throw errors.RATE_LIMITED({
    message: 'You are being rate limited',
    data: { retryAfter: 60 }
  })
  // OR --- both are equivalent
  throw new ORPCError('RATE_LIMITED', {
    message: 'You are being rate limited',
    data: { retryAfter: 60 }
  })
  return next()
})

const example = base
  .use(rateLimit)
  .handler(async ({ input }) => {
    throw new ORPCError('BAD_REQUEST') // <-- unknown error
  })

DANGER

Remember: Since ORPCError.data is transmitted to the client, do not include any sensitive information.

Released under the MIT License.