Grant Pitt
Grant Pitt

Reputation: 491

How to strongly type the response of SvelteKit endpoint in the component?

So it looks like I have to separately type the response body going out of an endpoint and the props that I get from that body in the component. For example:

// index.ts (endpoint)

import type { RequestHandler } from "./__types";

export const get: RequestHandler<{
  foo: number;
  bar: string;
}> = () => {
  return {
    body: {
      foo: 42,
      bar: "hello"
    }
  };
};
// index.svelte (component)

<script lang="ts">
  export let foo;  // Not typed?
  export let bar;
</script>

I guess my question is why am I typing the response going out if I don't benefit from that typing in the component props?

Upvotes: 6

Views: 2884

Answers (2)

Cole Crouter
Cole Crouter

Reputation: 145

As brunnerh notes, there isn't currently (as of writing this) a good way to take the strongly-typed component/endpoint, and pass its definition to the other. In the past, I've used the endpoint to store a "response" interface, which can easily be imported by co-located components.

// +server.ts
import { json } from '@sveltejs/kit';

export const GET = async () => {
    // ...
    return json({ type: "success", msg: "Hello, world!" } satisfies _res);
                                                       // ^ typed
};

export type _res = {
    type: "success" | "error";
    msg: string;
};
<!-- Component.svelte -->
<script lang="ts">
    import { onMount } from 'svelte';
    import type { _res } from './endpoint/+server';

    let text = '';
    onMount(async () => {
        const res = await fetch('/version/endpoint');
        const obj: _res = await res.json();
                 //^ typed
        text = obj.msg;
    });
</script>

{text}

Note on modern SvelteKit

I generally avoid fetching inside components, as it is usually messy.

In most cases, you can get away with data loading for GET, and form actions for POST. load() is strongly typed, thanks to codegen.

Once inside your component, you can achieve strong-typing by either:

  • creating an interface (following the example above)
  • importing the type from the page function:
<script lang="ts">
    import type { load } from './+page';
    export let data: Awaited<ReturnType<typeof load>>;
             //^ let data: { type: "success" | "error"; msg: string; }
</script>

You can return custom JSON in form actions, but it's not currently strongly typed. You have the option to combine data loading and form actions to achieve this, by migrating your API "response" logic into load():

form submitted -> server action -> invalidateAll() -> load() re-ran -> page updated

Upvotes: 0

brunnerh
brunnerh

Reputation: 185225

This simply seems unsupported at the moment. The only thing that is generated are the params along the path (placeholders like [id] in file names).

Also, the source of truth would be the component, not the other way around, as all endpoints besides get can just return whatever they want.


I tried to work around this using type inference, but the generated component types do not appear to be accessible in TS files. If someone knows a workaround for that, it would be easy to type the endpoint correctly.

// src/lib/typing.ts
import type { SvelteComponentTyped } from 'svelte';

export type ComponentProps<T> = T extends SvelteComponentTyped<infer P> ? P : never;
import type { RequestHandler } from './__types';
import type Index from './index.svelte';
import type { ComponentProps } from '$lib/typing';

type Props = ComponentProps<Index>; // Unfortunately resolved to `any`

export const get: RequestHandler<Props> = async () => {
  //...
}

Inside a Svelte file:

<script lang="ts">
    import type { ComponentProps } from '$lib/typing';
    import type Index from './index.svelte';

    type Props = ComponentProps<Index>;
</script>

vs code screenshot of type resolution

Upvotes: 3

Related Questions