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.
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:
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:
<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.