anton.johansson
anton.johanssonβ€’16mo ago

Custom error management

Hey peeps! I could've sworn I created a GitHub issue about this, but I must've been dreaming, because I cannot find it. Anyway, it's support-related, so I figured it's better to ask here! I have a large application that has a traditional client/server API and I use Axios to make calls to the server. Now, I'm investigating replacing that with tRPC (because it feels like an amazing developer experience and will quicken development). I have a rather "sophisticated" error management in the application. I have my own custom ApplicationError, which has an errorCode and a custom data object with additional information about the error. Any server-side code in the application can throw this error. A middleware catches this and converts it to a 400 Bad Request with an appropriate JSON response body. In the client, I also have my custom wrapper on top of Axios, that simply catches 400 Bad Request, and converts the JSON back to an ApplicationError and simply throws it. This means that I can easily catch ApplicationError in my client side code, just as if it was originally thrown on the client. Now, I want to do something similar with tRPC. I see that we have the TRPCError, but I would love to be able to utilize my ApplicationError. For two reasons: 1. I don't want to re-write all my code. 2. I think it's a nice abstract layer that the majority of my server side code do not need to be aware of "tRPC". Does anyone have any ideas on how I can achieve this? I would need some kind of middleware on top of the tRPC procedure that catches my error and converts it into a TRPCError. I would also need some kind of filter in my tRPC client code that automatically converts this back into an ApplicationError and throws it. Any help or suggestions are appreciated! Thanks in advance.
14 Replies
Nick
Nickβ€’16mo ago
errorFormatter and middlewares should do the trick You can remove a lot of your boilerplate because you’ve essentially described how tRPC handles errors too, albeit you probably won’t get back an instanceof your custom error class, just can add your extra data no problem An error catching middleware like before should be possible
anton.johansson
anton.johanssonβ€’15mo ago
Allright, I'll look into it, thanks Nick!
ensomnium
ensomniumβ€’15mo ago
Did you figure this out @anton-johansson ? I'm using sveltekit and in order to redirect we need to throw unwrapped errors. @Nick Lucas any ideas how to get an unwrapped error out?
anton.johansson
anton.johanssonβ€’15mo ago
Hmm, I'm still struggling a little bit with this. Is there no way to convert it back to my own custom error on the client side, in a "generic" way? It feels like it should be relatively easy to hook in where the error object is being created, and convert it back to my own error type. @Nick Lucas Any ideas? Digging deep into the tRPC code at the moment, and it looks like the error must be of type TRPCClientError<MyRouter>. Otherwise, I could probably write my own httpBatchLink with custom error logic, but it looks like I can't.
Nick
Nickβ€’15mo ago
I believe you could set up your transformer to achieve this Superjson can have custom types registered But it would be simpler to include a type key on the change so you can check the type of the error on the frontend
anton.johansson
anton.johanssonβ€’15mo ago
Well, I mostly want this because I feel it's clearer to handle my own error than a library-specific error. πŸ™‚ But it probably does not matter much. Anyway, I tried messing around with transformer, but it looks like the entire framework is tightly coupled with the TRPCClientError type. The TRPCLink type "forces" TRPCClientError. I'll see if I can easily work with TRPCClientError instead.
Nick
Nickβ€’15mo ago
All our errors I believe have cause keys, your error will be contained within and you can access it in the error formatter
anton.johansson
anton.johanssonβ€’15mo ago
Yeah, but I'd love to be able to do something like this:
const myAction = trpc.myAction.useMutation();
const onClick = async () => {
try {
await myAction.mutateAsync();
} catch (error: unknown) {
if (error instanceof ApplicationError) {
// handle my own error type
} else {
// show some generic error
}
}
};
const myAction = trpc.myAction.useMutation();
const onClick = async () => {
try {
await myAction.mutateAsync();
} catch (error: unknown) {
if (error instanceof ApplicationError) {
// handle my own error type
} else {
// show some generic error
}
}
};
But I think this won't work, I'll have to work with the TRPCClientError.
Nick
Nickβ€’15mo ago
Feel free to open a github issue, and PRs are welcome πŸ™‚
anton.johansson
anton.johanssonβ€’15mo ago
I had some thoughts first, but I think I can make it very similar with the TRPCClientError, I'm gonna mess around a bit with it. πŸ™‚ I just like the idea of having the same error on the server and on the client, but it's more a personal thing.
Nick
Nickβ€’15mo ago
I suspect the benefit isn't that clear though, using the error formatter to output an object with a type string you can switch on will produce the same effect, and serialising errors even with a transformer is likely to be a nightmare
anton.johansson
anton.johanssonβ€’15mo ago
Yeah I get it, I just figured that you could allow "converting" a TRPCClientError into my own error before throwing it, but I'm not sure if it's worth the effort.
Nick
Nickβ€’15mo ago
TRPCClientError does have a cause key by the way, worth checking if that already contains what you want
anton.johansson
anton.johanssonβ€’15mo ago
Yeah I'll check! Looks like the cause is undefined here So I'd have to work with the data instead, which is most likely fine. πŸ™‚