#Forced typesafe body

4 messages · Page 1 of 1 (latest)

ornate urchin
#

Has anyone tried to make inferable typesafe body using nuxt $fetch?

Most of the methods I tried did not work. The only one that seems promising is a wrapper around defineEventHandler that pollutes response

There is a simple prototype:

export function definePostEventHandler<TInput, TOutput>(
  input: (rawBody: unknown) => TInput,
  handler: (body: TInput, event: H3Event<EventHandlerRequest>) => Promise<TOutput>,
): EventHandler<EventHandlerRequest, Promise<[TOutput, TInput]>> {
  return defineEventHandler(async (event) => {
    return [handler(input(readBody(event)), event)] as any;
  });
}

// If you don't want input validation
definePostEventHandler.any = <T>(rawBody: unknown) => rawBody as T;

Usage:

export default definePostEventHandler(definePostEventHandler.any<{ value: string }>, async (body) => {
  return {
    long: `Hello ${JSON.stringify(body)}`,
  };
});

Frontend access:

type InternalApiPostKey = keyof {
  [K in keyof InternalApi as InternalApi[K] extends { post: any } ? K : never]: unknown
};

export async function fetchPost<TKey extends InternalApiPostKey>(key: TKey, data: InternalApi[TKey]['post'][1]) {
  const [response] = await ($fetch as any)(key, {
    method: 'POST',
    data,
  });

  return response;
}
lofty flintBOT
#

Hi @ornate urchin. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

I understand you're looking for ways to implement typesafe body handling with Nuxt's $fetch. Your approach is interesting, and there are some official solutions and alternatives worth considering.

Type Safety in Nuxt API Routes

Nuxt has been working on improving type safety for API routes. There are a few approaches you can consider:

Using readValidatedBody

H3 (the server engine used by Nuxt) provides a readValidatedBody utility that can help with type validation:

export default defineEventHandler(async (event) => {
  const body = await readValidatedBody(event, (body) => {
    // Your validation logic here
    return body as YourType;
  });
  
  return { body };
});

This approach is documented in the H3 documentation and provides both runtime validation and type safety.

Alternative Serializers

For more complex scenarios, you might want to use an alternative serializer. Nuxt provides an example of using SuperJSON:

// server/api/superjson.ts
import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // Workaround the type conversion
    toJSON() {
      return this
    }
  }

  // Serialize the output to string, using superjson
  return superjson.stringify(data) as unknown as typeof data
})

Then on the client:

import superjson from 'superjson'

const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
});

This approach is documented in Nuxt's data fetching guide.

Recent Improvements

#

Nuxt has been working on improving type safety for API routes. For example, there was a recent PR to fix the useFetch type to omit the body for GET requests, which shows the team is actively working on type safety improvements.

There's also been discussion about typed query and body for $fetch and useFetch, which is related to your question.

Your Custom Approach

Your custom approach with definePostEventHandler is creative and might work for your specific use case. However, it does have the drawback you mentioned of "polluting" the response by wrapping it in an array.

One thing to note is that your approach might be more complex than necessary if all you need is type validation. The built-in readValidatedBody might be sufficient for many cases.

If you're experiencing specific type inference issues, it might be worth checking if they're related to known issues like those described in this GitHub issue, where type inference for API routes was breaking due to TypeScript issues.

Would you like more specific guidance on any of these approaches or help troubleshooting your current implementation?

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: