{"id":13599183,"url":"https://github.com/icflorescu/trpc-sveltekit","last_synced_at":"2025-05-14T05:11:00.864Z","repository":{"id":37613373,"uuid":"457902550","full_name":"icflorescu/trpc-sveltekit","owner":"icflorescu","description":"End-to-end typesafe APIs with tRPC.io for your SvelteKit applications.","archived":false,"fork":false,"pushed_at":"2025-03-06T12:51:55.000Z","size":1266,"stargazers_count":823,"open_issues_count":13,"forks_count":41,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-03T20:33:43.093Z","etag":null,"topics":["svelte","sveltekit","sveltekit-adapter","trpc","typescript"],"latest_commit_sha":null,"homepage":"https://icflorescu.github.io/trpc-sveltekit","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icflorescu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"icflorescu"}},"created_at":"2022-02-10T18:36:00.000Z","updated_at":"2025-05-01T11:42:59.000Z","dependencies_parsed_at":"2023-02-19T09:01:49.037Z","dependency_job_id":"9f95e353-274d-4873-8172-48492a98a326","html_url":"https://github.com/icflorescu/trpc-sveltekit","commit_stats":{"total_commits":287,"total_committers":13,"mean_commits":"22.076923076923077","dds":"0.15679442508710806","last_synced_commit":"ad913bb0aa6878c5bd69cb712a6ee03ab909eced"},"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icflorescu%2Ftrpc-sveltekit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icflorescu%2Ftrpc-sveltekit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icflorescu%2Ftrpc-sveltekit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icflorescu%2Ftrpc-sveltekit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icflorescu","download_url":"https://codeload.github.com/icflorescu/trpc-sveltekit/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076850,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["svelte","sveltekit","sveltekit-adapter","trpc","typescript"],"created_at":"2024-08-01T17:01:00.587Z","updated_at":"2025-05-14T05:10:55.790Z","avatar_url":"https://github.com/icflorescu.png","language":"TypeScript","funding_links":["https://github.com/sponsors/icflorescu"],"categories":["Sites","TypeScript","typescript","Enhancers/Extensions"],"sub_categories":["The _How To's?_"],"readme":"# tRPC-SvelteKit\n\n![Publish NPM \u0026 deploy docs workflow](https://github.com/icflorescu/trpc-sveltekit/actions/workflows/publish-and-deploy.yml/badge.svg)  \n[![NPM version][npm-image]][npm-url]\n[![License][license-image]][license-url]\n[![Stars][stars-image]][stars-url]\n[![Last commit][last-commit-image]][repo-url]\n[![Closed issues][closed-issues-image]][closed-issues-url]\n[![Downloads][downloads-image]][npm-url]\n[![Language][language-image]][repo-url]\n[![Sponsor the author][sponsor-image]][sponsor-url]\n\nDocumentation available at [icflorescu.github.io/trpc-sveltekit](https://icflorescu.github.io/trpc-sveltekit/).\n\n[![tRPC-SvelteKit](https://user-images.githubusercontent.com/581999/204399415-18fddfb9-acdf-4e15-a945-a27f816e354e.png)](https://icflorescu.github.io/trpc-sveltekit/)\n\n\u003e Move fast and break nothing.  \n\u003e End-to-end typesafe APIs for your  \n\u003e SvelteKit applications.\n\n## Works with\n\n✅ `@sveltejs/adapter-node`  \n✅ `@sveltejs/adapter-vercel`  \n✅ `@sveltejs/adapter-netlify`  \n\n## Important\n\ntRPC-SvelteKit v3.x.x is compatible with tRPC v10.  \nIf you're using tRPC v9, use tRPC-SvelteKit v2.x.x. The old source code is available in the [trpc-v9](https://github.com/icflorescu/trpc-sveltekit/tree/trpc-v9) branch.\n\n## Quickstart\n\n### Install the package and its dependencies:\n\n```bash\nyarn add trpc-sveltekit @trpc/server @trpc/client\n```\n\u003cdiv id=\"create-your-trpc-router\"\u003e\u003c/div\u003e\n\n### Create your [tRPC router](https://trpc.io/docs/router):\n\n```ts\n// lib/trpc/router.ts\nimport type { Context } from '$lib/trpc/context';\nimport { initTRPC } from '@trpc/server';\nimport delay from 'delay';\n\nexport const t = initTRPC.context\u003cContext\u003e().create();\n\nexport const router = t.router({\n  greeting: t.procedure.query(async () =\u003e {\n    await delay(500); // 👈 simulate an expensive operation\n    return `Hello tRPC v10 @ ${new Date().toLocaleTimeString()}`;\n  })\n});\n\nexport type Router = typeof router;\n```\n\u003cdiv id=\"create-a-trpc-context\"\u003e\u003c/div\u003e\n\n### Create a [tRPC context](https://trpc.io/docs/context):\n\n```ts\n// lib/trpc/context.ts\nimport type { RequestEvent } from '@sveltejs/kit';\nimport type { inferAsyncReturnType } from '@trpc/server';\n\n// we're not using the event parameter is this example,\n// hence the eslint-disable rule\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport async function createContext(event: RequestEvent) {\n  return {\n    // context information\n  };\n}\n\nexport type Context = inferAsyncReturnType\u003ctypeof createContext\u003e;\n```\n\n### Add this handle to your SvelteKit app [hooks](https://kit.svelte.dev/docs/hooks):\n\n```ts\n// hooks.server.ts\nimport { createContext } from '$lib/trpc/context';\nimport { router } from '$lib/trpc/router';\nimport type { Handle } from '@sveltejs/kit';\nimport { createTRPCHandle } from 'trpc-sveltekit';\n\nexport const handle: Handle = createTRPCHandle({ router, createContext });\n```\n\n### Define a helper function to easily use the tRPC client in your pages:\n\n```ts\n// lib/trpc/client.ts\nimport type { Router } from '$lib/trpc/router';\nimport { createTRPCClient, type TRPCClientInit } from 'trpc-sveltekit';\n\nlet browserClient: ReturnType\u003ctypeof createTRPCClient\u003cRouter\u003e\u003e;\n\nexport function trpc(init?: TRPCClientInit) {\n  const isBrowser = typeof window !== 'undefined';\n  if (isBrowser \u0026\u0026 browserClient) return browserClient;\n  const client = createTRPCClient\u003cRouter\u003e({ init });\n  if (isBrowser) browserClient = client;\n  return client;\n}\n```\n\n### Call the tRPC procedures in your pages:\n\n```ts\n// routes/+page.svelte\n\u003cscript lang=\"ts\"\u003e\n  import { page } from '$app/stores';\n  import { trpc } from '$lib/trpc/client';\n\n  let greeting = 'press the button to load data';\n  let loading = false;\n\n  const loadData = async () =\u003e {\n    loading = true;\n    greeting = await trpc($page).greeting.query();\n    loading = false;\n  };\n\u003c/script\u003e\n\n\u003ch6\u003eLoading data in\u003cbr /\u003e\u003ccode\u003e+page.svelte\u003c/code\u003e\u003c/h6\u003e\n\n\u003ca\n  href=\"#load\"\n  role=\"button\"\n  class=\"secondary\"\n  aria-busy={loading}\n  on:click|preventDefault={loadData}\u003eLoad\u003c/a\n\u003e\n\u003cp\u003e{greeting}\u003c/p\u003e\n```\n## Examples\n\nThis repository contains a handful of examples:\n\n- [simple](https://github.com/icflorescu/trpc-sveltekit/tree/main/examples/simple)\n- [bookstall](https://github.com/icflorescu/trpc-sveltekit/tree/main/examples/bookstall)\n- [websocket](https://github.com/icflorescu/trpc-sveltekit/tree/main/examples/websocket)\n\n---\n\n## EXPERIMENTAL WebSocket support\n(courtesy of [@SrZorro](https://github.com/SrZorro))\n\nSvelteKit [doesn't (yet) offer WebSockets support](https://github.com/sveltejs/kit/issues/1491), 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](#websockets-implementation-details) to find out how this works under the hood).\n\n### Caveats\n\n- Works with [@sveltejs/adapter-node](https://www.npmjs.com/package/@sveltejs/adapter-node) **exclusively**;\n- The URL is hardcoded to `/trpc`;\n- When in websocket mode, all tRPC methods are handled by it; this could be changed at some point so that only `subscriptions` are handled by the WebSockets server;\n- Prerendering is not supported, since in the current implementation no WebSockets server is created when building/prerendering.\n\n### Install the package and its dependencies:\n\n```bash\nyarn add trpc-sveltekit @trpc/server @trpc/client @sveltejs/adapter-node ws\n```\n\n### Setup workarounds\n\nIn your `vite.config.ts`, add:\n\n```ts\nimport { sveltekit } from '@sveltejs/kit/vite';\nimport type { UserConfig } from 'vite';\n\nimport { vitePluginTrpcWebSocket } from 'trpc-sveltekit/websocket'; // ➕\n\nconst config: UserConfig = {\n  plugins: [\n    sveltekit(),\n    vitePluginTrpcWebSocket // ➕\n  ]\n};\n\nexport default config;\n```\n\nIn your `svelte.config.js`, modify:\n\n```ts\nimport adapter from '@sveltejs/adapter-node';    // ➕\n// import adapter from '@sveltejs/adapter-auto'; // ➖\n```\n\nCreate this file next to `package.json` your server entrypoint:\n\n```js\n// wsServer.js\nimport { SvelteKitTRPCWSServer } from \"trpc-sveltekit/websocket\";\n\nSvelteKitTRPCWSServer(import.meta.url);\n```\n\nIn your `package.json` `scripts`, modify the `start` command:\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"node ./wsServer\"\n  }\n}\n```\n\n### Create your tRPC router \u0026 context\n\n- [Create your tRPC router](#create-your-trpc-router)\n- [Create a tRPC context](#create-a-trpc-context)\n\n### Call this function from your SvelteKit app [server hooks](https://kit.svelte.dev/docs/hooks#server-hooks):\n\n```ts\n// hooks.server.ts\nimport { createContext } from '$lib/trpc/context';\nimport { router } from '$lib/trpc/router';\nimport { createTRPCWebSocketServer } from \"trpc-sveltekit/websocket\";\n\nimport { building } from '$app/environment';\n\nif (!building) createTRPCWebSocketServer({ router, createContext })\n```\n\n### Define a helper function to easily use the tRPC client in your pages:\n\n```ts\n// lib/trpc/client.ts\nimport type { Router } from '$lib/trpc/router';\nimport { createTRPCWebSocketClient } from \"trpc-sveltekit/websocket\";\n\nlet browserClient: ReturnType\u003ctypeof createTRPCWebSocketClient\u003cRouter\u003e\u003e;\n\nexport function trpc() {\n  const isBrowser = typeof window !== 'undefined';\n  if (isBrowser \u0026\u0026 browserClient) return browserClient;\n  const client = createTRPCWebSocketClient\u003cRouter\u003e();\n  if (isBrowser) browserClient = client;\n  return client;\n}\n```\n\n### Call the tRPC procedures in your pages:\n\n```ts\n// routes/+page.svelte\n\u003cscript lang=\"ts\"\u003e\n  import { trpc } from '$lib/trpc/client';\n\n  let greeting = 'press the button to load data';\n  let loading = false;\n\n  const loadData = async () =\u003e {\n    loading = true;\n    greeting = await trpc().greeting.query();\n    loading = false;\n  };\n\u003c/script\u003e\n\n\u003ch6\u003eLoading data in\u003cbr /\u003e\u003ccode\u003e+page.svelte\u003c/code\u003e\u003c/h6\u003e\n\n\u003ca\n  href=\"#load\"\n  role=\"button\"\n  class=\"secondary\"\n  aria-busy={loading}\n  on:click|preventDefault={loadData}\u003eLoad\u003c/a\n\u003e\n\u003cp\u003e{greeting}\u003c/p\u003e\n```\n\n\u003cdiv id=\"websockets-implementation-details\"\u003e\u003c/div\u003e\n\n### Implementation details\n\nAll the related code to the websocket implementation is located at `package/src/websocket`.\n\n#### `vitePlugin.ts`\n\nExports a vite plugin that handles in dev mode the websocket lifecycle.\n\n- On init: `configureServer`\n  - `createWSSGlobalInstance`\n  - Listen for `upgrade` events in vite dev server, so we can upgrade `/trpc` to our tRPC server\n\nOn 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.\n\nWe store a reference in `globalThis` to the web socket server, so we can later get a reference from SvelteKit side.\n\n\u003e To store the websocket server without colliding with existing stuff in `globalThis` at `src/websocket/svelteKitServer.ts` we create a `Symbol`  \n\u003e so we can reference the tRPC websocket like so: `globalThis[Symbol.for('trpc.sveltekit.wss')]`\n\nThen 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.\n\n#### `svelteKitServer.ts`\n\nExports functions to handle the lifecycle of the tRPC websocket server:\n\n- `createWSSGlobalInstance`\n- `onHttpServerUpgrade`\n\n\u003e The firsts 2 methods are already explained in the `vitePlugin.ts` section.\n\n- `SvelteKitTRPCWSServer`\n\nThe Vite plugin only works while the Vite dev server is running. When building for production we need to take a diferent aproach.\n\nWhen we build a SvelteKit app, it will output a `./build` directory.\n\nThis 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`.\n\nFirst 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.\n\nWe attach to this server `onHttpServerUpgrade` so we handle in the production server the tRPC websocket.\n\n#### `server.ts`\n\nThe function `createTRPCWebSocketServer` handles the creation of the websocket tRPC handler getting the `wss` from `globalThis`.\n\nThis 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`.\n\nThis is why, when calling this method, we have to add a guard on the client/consumer code:\n\n```ts\nimport { building } from '$app/environment';\n\nif (!building) // 👈 Prevent from calling when building/prerendering\n    createTRPCWebSocketServer({ router, createContext })\n```\n\n#### `client.ts`\n\n`createTRPCWebSocketClient`\n\nCreates the tRPC proxy client and links to the `wss`.\n\n\u003e Currently all the tRPC requests are handled via websockets, [but this could be changed to only handle subscriptions](https://trpc.io/docs/links).\n\n---\n\n## Contributors\n\n[![Contributors list](https://contrib.rocks/image?repo=icflorescu/trpc-sveltekit)](https://github.com/icflorescu/trpc-sveltekit/graphs/contributors)\n\n## Acknowledgements\n\nHuge thanks to [Alex / KATT](https://github.com/KATT), the author of [tRPC](https://trpc.io/), for being the first sponsor of this project! 🎉 \n\n## Stand with Ukraine\n\nOn 24th of February 2022 [Russia unlawfully invaded Ukraine](https://en.wikipedia.org/wiki/Russo-Ukrainian_War). 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](https://www.stopputin.net/).\n\n## License\n\nThe [ISC License](https://github.com/icflorescu/trpc-sveltekit/blob/master/LICENSE).\n\n[npm-url]: https://npmjs.org/package/trpc-sveltekit\n[repo-url]: https://github.com/icflorescu/trpc-sveltekit\n[stars-url]: https://github.com/icflorescu/trpc-sveltekit/stargazers\n[closed-issues-url]: https://github.com/icflorescu/trpc-sveltekit/issues?q=is%3Aissue+is%3Aclosed\n[license-url]: LICENSE\n[npm-image]: https://img.shields.io/npm/v/trpc-sveltekit.svg?style=flat-square\n[license-image]: http://img.shields.io/npm/l/trpc-sveltekit.svg?style=flat-square\n[downloads-image]: http://img.shields.io/npm/dm/trpc-sveltekit.svg?style=flat-square\n[stars-image]: https://img.shields.io/github/stars/icflorescu/trpc-sveltekit?style=flat-square\n[last-commit-image]: https://img.shields.io/github/last-commit/icflorescu/trpc-sveltekit?style=flat-square\n[closed-issues-image]: https://img.shields.io/github/issues-closed-raw/icflorescu/trpc-sveltekit?style=flat-square\n[language-image]: https://img.shields.io/github/languages/top/icflorescu/trpc-sveltekit?style=flat-square\n[sponsor-image]: https://img.shields.io/badge/sponsor-violet?style=flat-square\n[sponsor-url]: https://github.com/sponsors/icflorescu\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficflorescu%2Ftrpc-sveltekit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficflorescu%2Ftrpc-sveltekit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficflorescu%2Ftrpc-sveltekit/lists"}