working with custom errors and trpc errorFormatter

Hey guys. I'm trying to refactor my app so that all errors extend a BaseError class to make dealing with error codes and user-friendly messages easier. I'm having trouble converting these to the correct shape in the tRPC errorFormatter. Right now when I throw these custom errors tRPC doesnt recognize them and converts all of them to INTERNAL_SERVER_ERROR's. I see that some error codes get translated in 3 places: the jsonrpc error code, the http status code, and an HTTP status,. There also seems to be some additional structure. My end goal is to be able to convert my errors to the 'normal' shape so that trpc-openapi can correctly return them. Is there a reasonable way to do this in tRPC or should I do something different? Its easy enough for me to convert one of my errors to a TRPCError, but I don't see a way to convert that to the right shape once I'm in the errorFormatter. is there a way to do this?
N
Nick182d ago
It should pass through your errorFormatter and you can detect it and decide what to do with it. You probably want the error.cause property to access your thrown error Try just console logging everything out to start and then go from there
T
Tom182d ago
I got that far, but it seems like I need to be able to get a few fields that i dont really know how to generate: 1) shape.code (which looks to be some kind of JSONRPC code?) 2) shape.data httpStatus (is there an easy way to get this from a TRPCError code?) 3) can i add an additional customAppCode somewhere in this structure? do i put it in the root of the object or shape.data? I'm not really sure what the implications of any of this are. thanks for any help Ok so i did some more looking into this and now im more confused after some confusing results I added this to my errorFormatter():
errorFormatter(params) {
console.log("------------------------------");
console.log(params.shape);
console.log("------------------------------");

return {
message: "test",
code: -32004,
data: {
code: "NOT_FOUND",
httpStatus: 404,
stack: params.shape.data.stack,
path: params.shape.data.path,
},
};
}
errorFormatter(params) {
console.log("------------------------------");
console.log(params.shape);
console.log("------------------------------");

return {
message: "test",
code: -32004,
data: {
code: "NOT_FOUND",
httpStatus: 404,
stack: params.shape.data.stack,
path: params.shape.data.path,
},
};
}
this data exactly matches the data i get when i throw a regular TRPCError this seems to work in regular trpc (although im still not sure how i should be generating all these codes) but in trpc-openapi i always get a 500 if the original error isnt a TRPCError ok so after a bunch of trying things im most of the way there with this code:
errorFormatter(params) {
if (params.error.cause && params.error.cause instanceof BaseError) {
const httpCode = params.error.cause.getHttpStatusCode();

return {
message: params.error.cause.displayMessage ?? "Internal Server Error",
code: getJsonRpcErrorCode(httpCode),
data: {
code: params.error.cause.code,
httpStatus: httpCode,
path: params.shape.data.path,
stack: params.shape.data.stack,
},
};
} else {
return {
message: "Internal Server Error",
code: getJsonRpcErrorCode(500),
data: {
code: params.error.code,
httpStatus: 500,
path: params.shape.data.path,
stack: params.shape.data.path,
},
};
}
},
errorFormatter(params) {
if (params.error.cause && params.error.cause instanceof BaseError) {
const httpCode = params.error.cause.getHttpStatusCode();

return {
message: params.error.cause.displayMessage ?? "Internal Server Error",
code: getJsonRpcErrorCode(httpCode),
data: {
code: params.error.cause.code,
httpStatus: httpCode,
path: params.shape.data.path,
stack: params.shape.data.stack,
},
};
} else {
return {
message: "Internal Server Error",
code: getJsonRpcErrorCode(500),
data: {
code: params.error.code,
httpStatus: 500,
path: params.shape.data.path,
stack: params.shape.data.path,
},
};
}
},
but trpc-openapi doesnt completely work. I can change the http status code with this responseMeta() call:
responseMeta: (opts) => {
if (opts.errors[0]?.cause instanceof BaseError) {
return { status: opts.errors[0].cause.getHttpStatusCode() };
} else {
return { status: 500 };
}
},
responseMeta: (opts) => {
if (opts.errors[0]?.cause instanceof BaseError) {
return { status: opts.errors[0].cause.getHttpStatusCode() };
} else {
return { status: 500 };
}
},
but this still doesnt change the code that is actually sent down in the body:
curl -v localhost:3000/api/v1/test
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /api/v1/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 404 Not Found # <------------------------------------------ CORRECT ERROR CODE FROM APP
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Vary: Accept-Encoding
< Date: Thu, 19 Oct 2023 06:07:18 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
{"message":"test","code":"INTERNAL_SERVER_ERROR"}% # <----------------- ALWAYS INTERNAL_SERVER_ERROR
curl -v localhost:3000/api/v1/test
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /api/v1/test HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 404 Not Found # <------------------------------------------ CORRECT ERROR CODE FROM APP
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Vary: Accept-Encoding
< Date: Thu, 19 Oct 2023 06:07:18 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
{"message":"test","code":"INTERNAL_SERVER_ERROR"}% # <----------------- ALWAYS INTERNAL_SERVER_ERROR
it seems that the TRPCError.code variable is also readonly, do i cant change it im also not sure if im handling stack correctly. will it still properly get omitted in production builds?
N
Nick182d ago
It's really up to you how you use the errorFormatter, but I would avoid thinking in HTTP. This isn't REST, the whole point is to think in Typescript, so craft types that represent your errors and return them, then they'll be inferred and typesafe on the frontend I'm not really very familiar with trpc-openapi though, it's not a project I'm involved in or have used
T
Tom181d ago
I'm not trying to think in HTTP. openapi-trpc should do that for me (at least thats the theory) the problem im having is that i cant figure out how to take a non-trpc error in the error formatter and make it the same shape as a native trpc error if i only ever threw TRPCErrors then everything would work fine (which is what my app has been doing up to this point) but even if i know all the parameters that i would pass to a TRPCError, once its in the error formatter it seems that I lose the ability to replicate the same exact shape i guess a simpler, but still practical, example would be like lets say I have TRPCError({message: "You aren't allowed to see this", code: 'UNAUTHORIZED'}) but then i want to enforce a rule in my error formatter that says 'for security reasons, i dont want to return UNAUTHORIZED because that tells the client that this resource exists. I want to convert this to NOT_FOUND' as far as i can tell, i cant really do that is that accurate?
N
Nick181d ago
You don’t need to match the shape of the trpc error, there are a few keys that tRPC requests in the formatter that you can just copy over, but then you attach what you like and the frontend will get access There’s no need to throw any specific error class yourself, it’s totally fine to use custom ones
More Posts
tRPC and OpenTelemetryDoes anyone have experience with instrumenting tRPC with OTEL for tracing? It would be pretty neat Multiple TRPC endpoints on same API, with discrete clients? Any gotchas?I'm building a Sveltekit app that has several pages that will need to behave like SPA's. Rather thanProper way to revalidate SSR but from Client Side, ie. after a success Mutation.This question has been also posted on an specific thread but posting this here will allow people to How to implement tRPC auth ?Do you have any article/github links for how can i create auth in tRPC ?tRPC middleware infer type from another protectedProcedureHello, `protectedProcedure` check and adds a not nullable user to the ctx. However the `studyMiddlewtRPC requests on client vs server for NextJsHello, I'm new to tRPC, and am confused on one thing. I have watched videos of some using a server cpassing user input state variable to all api callsI have a multi organization app and need a way of passing the current organizationId on all requestsIs there a way to extract a procedure signature?I'm looking into extracting certain handlers into functions to keep certain procedures more manageabIs there a pattern for omitting certain types of errors from being returned in the API responseI'd prefer not to expose the ORM queries being used in my resolver. I'm already providing an `onErroDynamic input not leading to changes on the front endI'm building a dynamic procedure to access my database via Drizzle ORM. Ideally a developer would btRPC with with react-query prefetching in Next.js app directory.Hi. What is currently the best way to do prefetching in Next.js app directory when using @tanstack/rHello, is there any way to create a base router with common procedures? like an interfaceI want all routers that use it have the state procedure (subscriptions), so I can create a common hHelp Retrieving req.url from opts in useMutation from tRPCHello, i'm trying to essentially send a magiclink email using supabase client libraries, however, i'Express tRPC NextJSI've custom expressjs server with Nextjs + im using internal package for handling OneLogin (OIDC) hoDynamic links url dependent on next headers?Hi, We're currently explore if we can use trpc on a multi tenant next.js 13 application. But we cancalling TRPCProxyClient from inside trpc procedure fails on vercel route handlers edge runtimei setup my client as following: ``` export const serverToServerApi = createTRPCProxyClient<AppRouterPrivate procedures and serverside callsHello, I am just diving into trpc in my Next.js (app router) application and I am wondering if is thinferRouterOutputs ignore "complex data type" fields and only have primitive fields. Bug?Is this a bug ? I have done npx prisma db push, I have restarted VSCode and Typescript. Any commentsHow can i modify my URL using TRPCI did a simple pagination in react and trpc(a simple table), only problem is that: i want in my url TRPC EXPORT API TYPES BE to FEIs there a way I can automatically export my API types from the backend to the frontend? For example