By
Raqueebuddin Aziz
April 15, 2023 (Updated on April 17, 2023)
Freelance Web Designer & Developer
By
April 15, 2023 (Updated on April 17, 2023)
Freelance Web Designer & Developer
In this guide we will be adding tRPC to a new SvelteKit project, all these steps can also be used to add tRPC to an existing SvelteKit project.
To start create a new SvelteKit project and install @trpc/server, @trpc/client and zod.
npm create svelte@latest my-sveltekit-project-with-trpc
cd my-sveltekit-project-with-trpc
npm install @trpc/server @trpc/client zod
We will be creating the tRPC server in src/lib/server folder in the new SvelteKit project.
context.ts in src/lib/server folder with the following contents.// src/lib/server/context.ts
import { initTRPC } from '@trpc/server'
import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'
export const createSvelteKitContext = (locals: App.Locals) => (opts: FetchCreateContextFnOptions) =>
locals
const t = initTRPC.context<ReturnType<typeof createSvelteKitContext>>().create()
export const router = t.router
export const publicProcedure = t.procedure
App.Locals server context to tRPC server.router and publicProcedure methods so we can use them in our router next.router.ts in src/lib/server folder.// src/lib/server/router.ts
import { z } from 'zod'
import { publicProcedure, router } from './context'
export const appRouter = router({
greet: publicProcedure
.input(
z.object({
name: z.string()
})
)
.query(({ ctx, input }) => {
return `Hello ${input.name}`
})
})
export type AppRouter = typeof appRouter
router and publicProcedure from the context.ts file and z from zod for input validation for our procedures/endpoints.greet endpoint that takes an object as an input of form { name: string } and returns a string with a greeting.appRouter and it’s type AppRouter. The reason we export the type independently, so we don’t have to import our server code in the client when we create our tRPC client later.Now we need to serve the trpc server on our SvelteKit app.
To do this we will be creating a catchall route /api/trpc/[...procedure] so all requests under /api/trpc/ are handled by our tRPC server.
// src/routes/api/trpc/[...procedure]/+server.ts
import { createSvelteKitContext } from '$lib/server/context'
import { appRouter } from '$lib/server/router'
import type { RequestHandler } from '@sveltejs/kit'
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
export const GET = ((event) =>
fetchRequestHandler({
req: event.request,
router: appRouter,
endpoint: '/api/trpc',
createContext: createSvelteKitContext(event.locals)
})) satisfies RequestHandler
export const POST = ((event) =>
fetchRequestHandler({
req: event.request,
router: appRouter,
endpoint: '/api/trpc',
createContext: createSvelteKitContext(event.locals)
})) satisfies RequestHandler
fetchRequestHandler from @trpc/server/adapters/fetch to pass the request object to then we import our appRouter we created in the last step and pass that to the fetchRequestHandler as well, so it can recognize the greet procedure we defined.createSvelteKitContext helper we created in the last step so that we can pass our SvelteKit locals to our tRPC server.GET and POST handlers and pass all the data to the tRPC fetchRequestHandler.Let’s create the tRPC client in src/lib/trpc.ts file.
// src/lib/trpc.ts
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'
import type { FetchEsque } from '@trpc/client/dist/internals/types'
import type { AppRouter } from './server/router'
export const trpc = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })]
})
export const trpcOnServer = (fetch: FetchEsque) =>
createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: '/api/trpc',
fetch
})
]
})
AppRouter type and pass it to the createTRPCProxyClient, so we can get autocompletions in our tRPC client when we use it to call our endpoints/procedures.trpc client, so we can use it throughout our app. We also export the trpcOnServer function that creates a trpc instance with the sveltekit patched fetch which allows us to call our tRPC server in the same way as we call it in our client.Let’s see how to use everything we built till now. We will create a simple index page that will have an input where we type our name and the server responds with a greeting.
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { trpc } from '$lib/trpc';
let name: string = '';
let output: string = '';
$: name ? trpc.greet.query({ name }).then((value) => (output = value)) : (output = '');
</script>
<input bind:value={name} />
<p>{output}</p>
name and output register a side effect that calls the greeting method with our name when it changes and if name is empty string then we just set the output to an empty string rather than call the tRPC server.name to an input and showcase the output in a p tag.You are not limited to using the client in .svelte files, you can use them anywhere in your project including but not limited to +layout.ts, +layout.server.ts, +page.ts, +page.server.ts and other +server.ts files.
For example: You can ssr a name and output and update it on the client
Write the server code in +page.server.ts file:
// src/routes/+page.server.ts
import { trpcOnServer } from '$lib/trpc'
import type { PageLoad } from './$types'
export const load = (async ({ fetch }) => {
const trpc = trpcOnServer(fetch)
const name = 'Server Alias'
return { output: await trpc.greet.query({ name }), name }
}) satisfies PageLoad
Update the client code in +page.svelte files:
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { browser } from '$app/environment';
import { trpc } from '$lib/trpc';
export let data;
let name: string = data.name;
let output: string = data.output;
$: if (browser) {
name && trpc.greet.query({ name }).then((value) => (output = value));
}
</script>
<input bind:value={name} />
<p>{output}</p>
Setting up tRPC for the first time in a sveltekit project might seem daunting and seem like a chore but it is worth the effort to get less bugs and that sweet autocompletion. Plus you only need to do it once after that it’s just copy and paste.
Leave a comment down below if you have any questions or found this guide helpful.
HTML Anchor Tag Explained © 2025 Raqueebuddin Aziz. All rights reserved.