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.
© 2024 Raqueebuddin Aziz. All rights reserved.