ts-rest / ts-rest

RPC-like client, contract, and server implementation for a pure REST API
https://ts-rest.com
MIT License
2.11k stars 91 forks source link

Express contract errors are associated with handlers rather than exact code places #587

Closed ivan-kleshnin closed 1 month ago

ivan-kleshnin commented 2 months ago

I'm trying to migrate an app from Zodios to TS-REST.

I've noticed that, in the server code (uses@ts-rest/express), all contract errors are attached to / appear on handlers. For example:

// contract
export const contract = c.router({
  updateProfile: {
    method: "PUT",
    path: "/profile/update",
    body: z_updateProfileBody,
    responses: {
      ...
      404: z.object({message: z.literal("Not Found)}), // πŸ‘ˆ Expecting "Not Found" literal
    },
  }, 
})
// server
s.router(contract, {
  updateProfile: async ({body, req, res}) => { // ❗the error is displayed here
    ...
    return {status: "404", message: "some other message"} // πŸ‘ˆ instead of here
  })
})

It happens with both {name: handler} and {name: {middleware, handler}} interfaces.

The issue is that for larger handlers it gets quite inconvenient to scroll up, hover on handler, scroll the error popup, remember the name of the variable, close the popup, scroll down, try to fix, repeat... – every time there's an error.

Instead of noticing the error directly under the line it was triggered by. I'm not sure if it's possible to fix, I had similar issues myself with complex TS types and it was very tricky. I'm sure this case has a proper term which I didn't use, but I'm not a TypeScript expert, sorry.

I can make a code sandbox, if necessary.

It behaves differently (properly) in Zodios, I've just rechecked one more time to be sure.

Gabrola commented 1 month ago

This is due to how the handlers are defined and architected. ts-rest handlers need to just return a response, and the expected handler is defined by the expected return type of that handler. As a result, if you return an incorrect response, you are returning a type-incompatible handler, hence why the error is at the handler definition, not the the return value.

This isn't the case with zodios because in zodios, the responses are served by calling res.json not by returning the response.

The thing is that autocomplete still works fine in our approach, so there's very little reason to do something different. In your case if your handlers are too long, and you still want to see the error, maybe use Sticky Scroll/Sticky Lines in your editor. This should allow you to still see the first line of your handlers at the top of your editor window even when you scroll past it.

ivan-kleshnin commented 1 month ago

Maybe an explicit return type would help to pinpoint the error πŸ€”?

Gabrola commented 1 month ago

@ivan-kleshnin if you would like to do that, then sure, you can explicitly define the return type using Promise<ServerInferResponses<...>> but that's just worsening the DX.