tRPC-SvelteKit v3.6.2

End-to-end typesafe APIs for your SvelteKit applications

Recipes and caveats

Custom HTTP headers

The createTRPCClient method optionally accepts a headers option, which can be useful, for example, to set an Authorization header.

Setting response headers and cookies

To change the headers of the response, you want to use the event provided in getContext. You may also pass the event into the context, so that it can be accessed in your procedures.

You can then use event.setHeaders and event.cookies to edit the headers.

lib/trpc/context.ts
import type { RequestEvent } from '@sveltejs/kit';

export async function createContext(event: RequestEvent) {
  return {
    event // 👈 `event` is now available in your context
  };
}

export type Context = Awaited<ReturnType<typeof createContext>>;

Using custom data transformers

The createTRPCClient method optionally accepts a transformer option. Please refer to this section in the tRPC.io documentation for more information, and keep in mind that you'll have to use the same transformer when you're defining the router and when you're creating the client.

If you're looking for a convenient transformer based on superjson with Decimal.js support, consider using the trpc-transformer package. Keep in mind that you'll have to install the superjson and decimal.js peer dependencies as well.

Response caching

Your server responses must satisfy some criteria in order for them to be cached (i.e. by Vercel's Edge Network). Please refer to this section of the tRPC.io documentation for more information.

The createHTTPHandle method conveniently allows you to specify a responseMeta function.

Inferring types

It is often useful to wrap functionality of your tRPC client API within other functions. For this purpose, it's necessary to be able to infer input types and output types generated by your tRPC router. The@trpc/server package exports two helper types to assist with inferring these types from your router, namely inferRouterInputs and inferRouterOutputs. Please refer to this section of the tRPC.io documentation for more information.

To make your code faster to type and easier to read, you could further refine these helper types when defining your router:

lib/trpc/router.ts
import { authors } from '$lib/trpc/routes/authors';
import { books } from '$lib/trpc/routes/books';
import { stores } from '$lib/trpc/routes/stores';
import { t } from '$lib/trpc/t';
import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';

export const router = t.router({
  authors,
  books,
  stores
});

export const createCaller = t.createCallerFactory(router);

export type Router = typeof router;

// 👇 type helpers 💡
export type RouterInputs = inferRouterInputs<Router>;
export type RouterOutputs = inferRouterOutputs<Router>;

Then, you could use the helper types in your pages code like so:

routes/authors/+page.svelte
<script lang="ts">
  import { invalidateAll } from '$app/navigation';
  import AuthorizationAlert from '$lib/components/AuthorizationAlert.svelte';
  import DataTable from '$lib/components/DataTable.svelte';
  import ModalEditor from '$lib/components/ModalEditor.svelte';
  import TextInput from '$lib/components/inputs/TextInput.svelte';
  import TextareaInput from '$lib/components/inputs/TextareaInput.svelte';
  import { trpc } from '$lib/trpc/client';
  import type { RouterInputs } from '$lib/trpc/router';
  import { TRPCClientError } from '@trpc/client';
  import type { PageData } from './$types';

  export let data: PageData;

  let busy = false;
  let item: RouterInputs['authors']['save'] | null; // 👈 we're using a helper type
  let errors: { message: string; path: string[] }[] | null = null;
  let needsAuthorization = false;

  const handleAdd = async () => {
    if (!data.isAuthenticated) {
      needsAuthorization = true;
      return;
    }

    item = { id: null, firstName: '', lastName: '', bio: '' };
  };

  const handleEdit = async (e: CustomEvent<string>) => {
    if (!data.isAuthenticated) {
      needsAuthorization = true;
      return;
    }

    busy = true;
    item = await trpc().authors.load.query(e.detail);
    busy = false;
  };

  const handleDelete = async (e: CustomEvent<string>) => {
    if (!data.isAuthenticated) {
      needsAuthorization = true;
      return;
    }

    busy = true;
    await trpc().authors.delete.mutate(e.detail);
    await invalidateAll();
    busy = false;
  };

  const handleCancel = () => {
    item = null;
    errors = null;
  };

  const handleSave = async (e: CustomEvent<RouterInputs['authors']['save']>) => {
    if (!data.isAuthenticated) {
      needsAuthorization = true;
      return;
    }

    busy = true;
    try {
      await trpc().authors.save.mutate(e.detail);
      item = null;
      await invalidateAll();
    } catch (err) {
      if (err instanceof TRPCClientError) {
        errors = JSON.parse(err.message);
      } else {
        throw err;
      }
    } finally {
      busy = false;
    }
  };
</script>

<svelte:head>
  <title>Authors • Bookstall</title>
</svelte:head>

<DataTable
  {busy}
  title="Authors"
  items={data.authors}
  columns={[
    {
      title: 'Name',
      grow: true,
      accessor: ({ firstName, lastName }) => `${firstName} ${lastName}`
    },
    {
      title: 'Books',
      align: 'right',
      accessor: (author) => author._count.books
    }
  ]}
  on:add={handleAdd}
  on:edit={handleEdit}
  on:delete={handleDelete}
/>

<ModalEditor {item} itemName="author" on:cancel={handleCancel} on:save={handleSave}>
  <div class="grid">
    <TextInput name="firstName" label="First name" required {errors} {item} />
    <TextInput name="lastName" label="Last name" required {errors} {item} />
  </div>
  <TextareaInput name="bio" label="Bio" {errors} {item} />
</ModalEditor>

<AuthorizationAlert visible={needsAuthorization} on:close={() => (needsAuthorization = false)} />

Reserved path prefix

Invoking the createHTTPHandle method reserves the /trpc path prefix for the tRPC API. This means that you cannot use this prefix for any other purpose. If you need to use this prefix for other purposes, you can use the url option to change the reserved prefix.