nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Better typed `fetch`/`$fetch` and routes

Open pi0 opened this issue 1 year ago • 21 comments

Context, work, and explanation by @danielroe: https://github.com/unjs/nitro/pull/1532

We are thinking of improving (auto-generated) typed fetch. This requires adopting a new standard/reusable type utility for fetch, which @danielroe is working on. By using the new layer, we can improve performance and consistency and adopt it in several places instead of directly being in nitro core (benefiting nuxt, and in the next steps ofetch and h3 typed fetch support.)

Update: unjs/fetchdts is WIP project for better basis replacement.

Relevant linked issues: (some might be doable in next steps)

  • #3507
  • #3355
  • #2689
  • #2583
  • #1937
  • #1138
  • #938
  • #841
  • #470
  • https://github.com/nuxt/nuxt/issues/19077#issuecomment-2887519760

pi0 avatar Sep 28 '24 07:09 pi0

Improving fetch types would be an immense QOL improvement. I for one am very excited about this and am looking forward to more info as y'all push forward with it.

ImBoop avatar Sep 28 '24 18:09 ImBoop

Will this also type readBody to match the json serialization similiar to https://github.com/unjs/nitro/pull/1002?

image

And will this support alternative serializer to maintain type safety similiar to https://nuxt.com/docs/getting-started/data-fetching#using-an-alternative-serializer?

NothingEverWorks avatar Sep 28 '24 19:09 NothingEverWorks

Is this still planned? To my knowledge, it's one of the things gating Nuxt 4

ImBoop avatar Oct 25 '24 07:10 ImBoop

Hi, to type route files ([..].method.ts) should we use declare module '../relapth/of/the/file' {...} or there is a better way to apply types to a perticular file ?

flapili avatar Jan 20 '25 07:01 flapili

@flapili I don't believe it's possible without writing a typescript plugin

danielroe avatar Jan 20 '25 08:01 danielroe

@flapili I don't believe it's possible without writing a typescript plugin

Hi @danielroe , is it possible without any plugin in typescript you can, as example do in a referenced d.ts file

declare module "path/to/nuxt.config" {
   function foo(a: 'a'): number
}

in the nuxt.config.ts

const x = foo('a')

Of course that will not handle errors at runtime, here foo not declared

flapili avatar Jan 20 '25 09:01 flapili

@flapili that's great news - and must be new! the last time I tried it it did not work.

danielroe avatar Jan 20 '25 09:01 danielroe

At least it work in nuxt (maybe something in the tsconfig make it work/ not a default feature ?)

flapili avatar Jan 20 '25 09:01 flapili

hi, concretely what is possible / done / todo ?

Shouldn't we rewrite this issue first comment to make it a proper tracker ? or does github project page could be used to track (I don't know well this github feature) ?

currently it's hard to know this current state of each items cc @pi0 @danielroe

flapili avatar Jan 22 '25 07:01 flapili

@flapili I'm actively working on this at the moment and will be publishing a new (type-only) fetch-dts library to handle this in a generic way that can be used by nitro/ofetch/nuxt under the hood. pooya rightly pinged me about it as it's been a while.

danielroe avatar Jan 22 '25 16:01 danielroe

I am returning a branded type from my backend, something like this

type Role = number & { readonly __role: unique symbol };

export default defineEventHandler(() => {
  return {role: 1 as Role};
})

in Nuxt it shows as

type Response = {
  role: {
    toString: {};
    toFixed: {};
    toExponential: {};
    toPrecision: {};
    valueOf: {};
    toLocaleString: {};
    readonly __role: unique symbol;
    };
}

kaaax0815 avatar Feb 11 '25 11:02 kaaax0815

Hello, can't wait for this (really hoping to replace tRPC) ! Is there a current plan for body/queryParams validation ? Something like https://github.com/kevinmarrec/h3-typebox Also, having to pass the response type at first in defineEventHandler prevents from using type inference, is there an alternative?

Maybe less important, but is there a plan for a "better" openapi swagger generation (https://nitro.build/config#openapi) ? With body/params and response types, would be awesome!

Thanks a lot for the great work :)

sam-eah avatar Feb 11 '25 12:02 sam-eah

Is there a current plan for body/queryParams validation

There already is https://h3.unjs.io/examples/validate-data

kaaax0815 avatar Feb 11 '25 12:02 kaaax0815

It's been a few months, and I'm wondering what's the status on this? To be frank, I am having a difficult time with TypeScript performance on the front-end side as my project grows, and I'm not sure what are the best practices anymore if working exclusively with Nuxt / Nitro.

I assume my problems are related to this issue, as I experience TypeScript errors 2589 (Type instantiation is excessively deep and possibly infinite) and 2321 (Excessive stack depth comparing types) frequently, and I use the Nitro types FetchResult and SerializeObject (with Prisma's generated types, which adds more load, I'm sure) pretty extensively throughout my client code to try and match the expected inputs / outputs of my server code when defining refs and such.

I've considered using tRPC like mentioned above, but my project has reached a point where it would be a lot of refactoring, when I'm not even sure if it would fix the performance issues I'm having.

Tenrys avatar May 07 '25 11:05 Tenrys

@Tenrys I was having issues with typescript performance a lot as well. It is still slow, but if you by any chance are using the vscode extension "Pretty Type Errors" try turning that off.

As I understand it, this issue is dependent on a bunch of upstream stuff, so I think it will eventually get there.

madsh93 avatar May 07 '25 12:05 madsh93

It's been a few months, and I'm wondering what's the status on this? To be frank, I am having a difficult time with TypeScript performance on the front-end side as my project grows, and I'm not sure what are the best practices anymore if working exclusively with Nuxt / Nitro.

I assume my problems are related to this issue, as I experience TypeScript errors 2589 (Type instantiation is excessively deep and possibly infinite) and 2321 (Excessive stack depth comparing types) frequently, and I use the Nitro types FetchResult and SerializeObject (with Prisma's generated types, which adds more load, I'm sure) pretty extensively throughout my client code to try and match the expected inputs / outputs of my server code when defining refs and such.

I've considered using tRPC like mentioned above, but my project has reached a point where it would be a lot of refactoring, when I'm not even sure if it would fix the performance issues I'm having.

On my Nitro project, I started using shared types (ts and arktype) that are in shared package. Thus it's known what the API should return, but you have to change it when you want to change the response, which is fine in my opinion.

thebrownfox avatar May 12 '25 05:05 thebrownfox

How about using something like this:

// server/api/user/[id].patch.ts
import { z } from "zod"; // zod used as an example

export default defineEventHandler({
  validatedRouteParams: z.object({ id: z.coerce.number() }),
  // validatedQueryParams: null,
  validatedBody: z.object({ name: z.string(), age: z.number() }),
  // response: z.object({ name: z.string(), age: z.number() }), // inferred?
  // status: 200 // sucess status code of the response, default to 200
}, async ({ event, body, routeParams, queryParams }) => {
  return body
})

This would make it possible for $fetch to typecheck body, route params and query params.

I think this could also be used to generate openapi swagger files/doc.

It would also be nice to have an option to use superjson for payload/reponse.

Regarding route params, we could also have them accessed like this:

$fetch("/user/[id]", {
  routeParams: { id: 123 },
})

This would make passing urls to the $fetch function typesafe as well. (Inspired by openapi-typescript / nuxt-open-fetch)

Inspirations:

  • https://trpc.io/
  • https://www.zodios.org/ (https://www.zodios.org/docs/api/openapi)
  • https://ez.robintail.cz/v23.4.0/quick-start
  • https://github.com/Foundry376/express-zod-openapi-autogen

Edit: Another interesting project : https://orpc.unnoq.com/ (typesafe, openapi compatible - can be used to generate a swagger, validation, integrates with tanstack query, supports SSE and Streaming)

sam-eah avatar May 16 '25 13:05 sam-eah

One small input from me.

When a project contains a file with "TS2321: Excessive stack depth comparing types" error, then nuxi typecheck also finishes with an error like this:

Found 2 errors in the same file, starting at: app/composables/useFetchPage.ts:9

 ERROR  Process exited with non-zero status (2)

    at R._waitForOutput (/C:/Users/user/ws/project/node_modules/tinyexec/dist/main.js:530:13)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Object.run (/C:/Users/user/ws/project/node_modules/@nuxt/cli/dist/chunks/typecheck.mjs:70:9)
    at async runCommand (/C:/Users/user/ws/project/node_modules/citty/dist/index.mjs:316:16)
    at async runCommand (/C:/Users/user/ws/project/node_modules/citty/dist/index.mjs:307:11)
    at async runMain (/C:/Users/user/ws/project/node_modules/citty/dist/index.mjs:445:7)

 ERROR  Process exited with non-zero status (2)

nuxi typecheck still correctly reports some other unrelated errors in other files, but I'm not sure if it reports all errors in the project or only those errors that are happened during the check before "Excessive stack depth" error occured and as soon as it occured, nuxi typecheck just terminates its execution (skipping checking of other files in the project). In other words, I'm not sure if there is no TS errors in my project because of this and how to check it without opening/checking files one-by-one.

xak2000 avatar May 28 '25 18:05 xak2000

I've had a similar issue like @xak2000, type errors only occur when I run a typecheck script, not within VS Code itself. Running bun --bun typecheck resolved this for me.

RizkiNfs avatar Aug 25 '25 08:08 RizkiNfs

As mentionned in #470, we encountered this in the back using the latest nitro. We have like 25 api routes. I saw NitroFetchRequest displaying all our routes

Is this subject still WIP ?

BaptisteCrouzet avatar Oct 29 '25 15:10 BaptisteCrouzet

The issues related to this are significantly impacting the development experience in Nuxt in larger projects. One of Nuxt's most valuable features is the inferred types for server routes, but issues like this cause tsserver to become extremely slow (which is already a concern even under normal conditions). For reference, see https://github.com/nuxt/nuxt/issues/33838

The only workaround that has been effective for us is removing the generated server route types. However, this isn't an ideal solution, as the types need to be removed again each time the server restarts or anything triggers type regeneration. It would be helpful to have an option to disable type generation for server routes (if such an option already exists, I haven't been able to locate it).

If there are any suggestions to make the development experience more manageable, I would greatly appreciate them.

8uff3r avatar Dec 09 '25 07:12 8uff3r