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.
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.
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.
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.