Documentation available at icflorescu.github.io/trpc-sveltekit.
Move fast and break nothing.
End-to-end typesafe APIs for your
SvelteKit applications.
β
@sveltejs/adapter-node
β
@sveltejs/adapter-vercel
β
@sveltejs/adapter-netlify
tRPC-SvelteKit v3.x.x is compatible with tRPC v10.
If you're using tRPC v9, use tRPC-SvelteKit v2.x.x. The old source code is available in the trpc-v9 branch.
yarn add trpc-sveltekit @trpc/server @trpc/client
// lib/trpc/router.ts
import type { Context } from '$lib/trpc/context';
import { initTRPC } from '@trpc/server';
import delay from 'delay';
export const t = initTRPC.context<Context>().create();
export const router = t.router({
greeting: t.procedure.query(async () => {
await delay(500); // π simulate an expensive operation
return `Hello tRPC v10 @ ${new Date().toLocaleTimeString()}`;
})
});
export type Router = typeof router;
// lib/trpc/context.ts
import type { RequestEvent } from '@sveltejs/kit';
import type { inferAsyncReturnType } from '@trpc/server';
// we're not using the event parameter is this example,
// hence the eslint-disable rule
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function createContext(event: RequestEvent) {
return {
// context information
};
}
export type Context = inferAsyncReturnType<typeof createContext>;
// hooks.server.ts
import { createContext } from '$lib/trpc/context';
import { router } from '$lib/trpc/router';
import type { Handle } from '@sveltejs/kit';
import { createTRPCHandle } from 'trpc-sveltekit';
export const handle: Handle = createTRPCHandle({ router, createContext });
// lib/trpc/client.ts
import type { Router } from '$lib/trpc/router';
import { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';
let browserClient: ReturnType<typeof createTRPCClient<Router>>;
export function trpc(init?: TRPCClientInit) {
const isBrowser = typeof window !== 'undefined';
if (isBrowser && browserClient) return browserClient;
const client = createTRPCClient<Router>({ init });
if (isBrowser) browserClient = client;
return client;
}
// routes/+page.svelte
<script lang="ts">
import { page } from '$app/stores';
import { trpc } from '$lib/trpc/client';
let greeting = 'press the button to load data';
let loading = false;
const loadData = async () => {
loading = true;
greeting = await trpc($page).greeting.query();
loading = false;
};
</script>
<h6>Loading data in<br /><code>+page.svelte</code></h6>
<a
href="#load"
role="button"
class="secondary"
aria-busy={loading}
on:click|preventDefault={loadData}>Load</a
>
<p>{greeting}</p>
This repository contains a handful of examples:
(courtesy of @SrZorro)
SvelteKit doesn't (yet) offer WebSockets support, but if you're using @sveltejs/adapter-node, tRPC-SvelteKit can spin up an experimental WS server to process tRPC procedure calls (see the implementation details to find out how this works under the hood).
/trpc;subscriptions are handled by the WebSockets server;yarn add trpc-sveltekit @trpc/server @trpc/client @sveltejs/adapter-node ws
In your vite.config.ts, add:
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
import { vitePluginTrpcWebSocket } from 'trpc-sveltekit/websocket'; // β
const config: UserConfig = {
plugins: [
sveltekit(),
vitePluginTrpcWebSocket // β
]
};
export default config;
In your svelte.config.js, modify:
import adapter from '@sveltejs/adapter-node'; // β
// import adapter from '@sveltejs/adapter-auto'; // β
Create this file next to package.json your server entrypoint:
// wsServer.js
import { SvelteKitTRPCWSServer } from "trpc-sveltekit/websocket";
SvelteKitTRPCWSServer(import.meta.url);
In your package.json scripts, modify the start command:
{
"scripts": {
"start": "node ./wsServer"
}
}
// hooks.server.ts
import { createContext } from '$lib/trpc/context';
import { router } from '$lib/trpc/router';
import { createTRPCWebSocketServer } from "trpc-sveltekit/websocket";
import { building } from '$app/environment';
if (!building) createTRPCWebSocketServer({ router, createContext })
// lib/trpc/client.ts
import type { Router } from '$lib/trpc/router';
import { createTRPCWebSocketClient } from "trpc-sveltekit/websocket";
let browserClient: ReturnType<typeof createTRPCWebSocketClient<Router>>;
export function trpc() {
const isBrowser = typeof window !== 'undefined';
if (isBrowser && browserClient) return browserClient;
const client = createTRPCWebSocketClient<Router>();
if (isBrowser) browserClient = client;
return client;
}
// routes/+page.svelte
<script lang="ts">
import { trpc } from '$lib/trpc/client';
let greeting = 'press the button to load data';
let loading = false;
const loadData = async () => {
loading = true;
greeting = await trpc().greeting.query();
loading = false;
};
</script>
<h6>Loading data in<br /><code>+page.svelte</code></h6>
<a
href="#load"
role="button"
class="secondary"
aria-busy={loading}
on:click|preventDefault={loadData}>Load</a
>
<p>{greeting}</p>
All the related code to the websocket implementation is located at package/src/websocket.
vitePlugin.tsExports a vite plugin that handles in dev mode the websocket lifecycle.
configureServercreateWSSGlobalInstanceupgrade events in vite dev server, so we can upgrade /trpc to our tRPC serverOn init we create a WebSocketServer with the property noServer so we can handle the upgrade to our tRPC and don't break the default vite websocket.
We store a reference in globalThis to the web socket server, so we can later get a reference from SvelteKit side.
To store the websocket server without colliding with existing stuff in
globalThisatsrc/websocket/svelteKitServer.tswe create aSymbol
so we can reference the tRPC websocket like so:globalThis[Symbol.for('trpc.sveltekit.wss')]
Then we set up an event listener to the vite dev http server to handle the upgrade event from onHttpServerUpgrade. It will check that the path is /trpc, if so it will upgrade our request to our tRPC websocket server.
svelteKitServer.tsExports functions to handle the lifecycle of the tRPC websocket server:
createWSSGlobalInstanceonHttpServerUpgradeThe firsts 2 methods are already explained in the
vitePlugin.tssection.
SvelteKitTRPCWSServerThe Vite plugin only works while the Vite dev server is running. When building for production we need to take a diferent aproach.
When we build a SvelteKit app, it will output a ./build directory.
This function takes import.meta.url as an argument from the root directory of the project (next to package.json) and then converts it to __dirname.
First it creates a websocket server attached to globalThis, as explained, then imports dynamically from ${__dirname}/build directory the index.js file, that exports a server property that contains an http server.
We attach to this server onHttpServerUpgrade so we handle in the production server the tRPC websocket.
server.tsThe function createTRPCWebSocketServer handles the creation of the websocket tRPC handler getting the wss from globalThis.
This current implementation in case we are prerendering would fail as vite does not call configureServer on the build step, so no wss server is found in globalThis.
This is why, when calling this method, we have to add a guard on the client/consumer code:
import { building } from '$app/environment';
if (!building) // π Prevent from calling when building/prerendering
createTRPCWebSocketServer({ router, createContext })
client.tscreateTRPCWebSocketClient
Creates the tRPC proxy client and links to the wss.
Currently all the tRPC requests are handled via websockets, but this could be changed to only handle subscriptions.
Huge thanks to Alex / KATT, the author of tRPC, for being the first sponsor of this project! π
On 24th of February 2022 Russia unlawfully invaded Ukraine. This is an unjustified, unprovoked attack on the sovereignty of a neighboring country, but also an open affront to international peace and stability that has the potential to degenerate into a nuclear event threatening the very existence of humanity. I am an EU (Romanian) citizen, but I am doing everything in my power to stop this madness. I stand with Ukraine. The entire Svelte community β€οΈπΊπ¦. Here's how you can show your support.
The ISC License.