• Stars
    star
    752
  • Rank 60,353 (Top 2 %)
  • Language
    TypeScript
  • License
    ISC License
  • Created almost 3 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

End-to-end typesafe APIs with tRPC.io for your SvelteKit applications.

tRPC-SvelteKit

Publish NPM & deploy docs workflow
NPM version License Stars Last commit Closed issues Downloads Language

Documentation available at icflorescu.github.io/trpc-sveltekit.

tRPC-SvelteKit

Move fast and break nothing.
End-to-end typesafe APIs for your
SvelteKit applications.

Works with

โœ… @sveltejs/adapter-node
โœ… @sveltejs/adapter-vercel
โœ… @sveltejs/adapter-netlify

Important

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.

Quickstart

Install the package and its dependencies:

yarn add trpc-sveltekit @trpc/server @trpc/client

Create your tRPC router:

// 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;

Create a tRPC context:

// 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>;

Add this handle to your SvelteKit app hooks:

// 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 });

Define a helper function to easily use the tRPC client in your pages:

// 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;
}

Call the tRPC procedures in your pages:

// 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>

Examples

This repository contains a handful of examples:


EXPERIMENTAL WebSocket support

(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).

Caveats

  • Works with @sveltejs/adapter-node exclusively;
  • The URL is hardcoded to /trpc;
  • 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;
  • Prerendering is not supported, since in the current implementation no WebSockets server is created when building/prerendering.

Install the package and its dependencies:

yarn add trpc-sveltekit @trpc/server @trpc/client @sveltejs/adapter-node ws

Setup workarounds

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"
  }
}

Create your tRPC router & context

Call this function from your SvelteKit app server hooks:

// 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 })

Define a helper function to easily use the tRPC client in your pages:

// 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;
}

Call the tRPC procedures in your pages:

// 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>

Implementation details

All the related code to the websocket implementation is located at package/src/websocket.

vitePlugin.ts

Exports a vite plugin that handles in dev mode the websocket lifecycle.

  • On init: configureServer
    • createWSSGlobalInstance
    • Listen for upgrade events in vite dev server, so we can upgrade /trpc to our tRPC server

On 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 globalThis at src/websocket/svelteKitServer.ts we create a Symbol
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.ts

Exports functions to handle the lifecycle of the tRPC websocket server:

  • createWSSGlobalInstance
  • onHttpServerUpgrade

The firsts 2 methods are already explained in the vitePlugin.ts section.

  • SvelteKitTRPCWSServer

The 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.ts

The 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.ts

createTRPCWebSocketClient

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.


Contributors

Contributors list

Acknowledgements

Huge thanks to Alex / KATT, the author of tRPC, for being the first sponsor of this project! ๐ŸŽ‰

Stand with Ukraine

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.

License

The ISC License.

More Repositories

1

mantine-datatable

The table component for your Mantine data-rich applications, supporting asynchronous data loading, column sorting, custom cell data rendering, context menus, nesting, Gmail-style batch row selection, dark theme, and more.
TypeScript
877
star
2

iisexpress-proxy

A simple local proxy for accessing IIS Express from remote machines.
JavaScript
740
star
3

openshift-cartridge-nodejs

Custom cartridge for OpenShift providing the lastest version of Node.js.
Shell
221
star
4

trpc-sveltekit-example

A sample SvelteKit application built to illustrate the usage of โœจ trpc-sveltekit.
Svelte
133
star
5

mantine-contextmenu

Craft your applications for productivity and meet your usersโ€™ expectations by enhancing your Mantine-based UIs with a desktop-grade, lightweight yet fully-featured, dark-theme aware context-menu component, built by the creator of Mantine DataTable.
TypeScript
121
star
6

expose-wsl

The simplest way to expose apps running on WSL to local network devices.
JavaScript
106
star
7

openshift-cartridge-mongodb

Custom cartridge for OpenShift providing MongoDB > 3.2.
Shell
70
star
8

textdiff-create

Create lean text diff deltas.
JavaScript
39
star
9

aspax

The simple Node.js asset packager.
CoffeeScript
17
star
10

textdiff-patch

Apply lean text diff delta patches created by textdiff-create.
JavaScript
17
star
11

quickstart-express-react

Combined live/hot-reloading quickstart for Express.js/React
JavaScript
16
star
12

trpc-transformer

A simple tRPC transformer based on superjson with decimal.js support
JavaScript
16
star
13

openshift-cartridge-mysql

Custom cartridge for OpenShift providing MySQL 5.7.17
Shell
13
star
14

vlc-3-appimage

A repository for building a VLC 3.0.3 AppImage on Travis CI
Shell
11
star
15

postgresql-tsearch-utils

A collection of files and patterns to improve PostgreSQL text search
11
star
16

aspa

End of Life Notice - January 2014: Please use ASPAX instead.
CoffeeScript
9
star
17

aspa-express

ASPA-Express is an Express/Connect module to use web assets packaged by ASPA
JavaScript
9
star
18

interiordelight.ro.v1

interiordelight.ro
JavaScript
7
star
19

quick-romanian-freelance-tax-calculator

A quick & dirty tax calculator for Romanian freelancers.
Svelte
6
star
20

openshift-cartridge-postgresql

Custom OpenShift cartridge aiming to provide the most recent stable PostgreSQL version packaged by bigsql.org.
Shell
6
star
21

gulp-js-with-nancy-fx

A Node.js-based front-end development workflow for ASP.NET
CSS
5
star
22

icflorescu

Ionut-Cristian Florescu
2
star
23

mantine-contextmenu-v6

The lightweight yet customisable context-menu for your Mantine applications, with a succinct API and dark-theme support. Compatible with Mantine V6.
TypeScript
2
star
24

qilive-q8912-kbd-driver-win-ro

Driver Windows pentru tastaturฤƒ Bluetooth cu layout romรขnesc Q.8912 de la Qilive
2
star
25

aspax-styl-handler

Plugin enabling ASPAX to handle Stylus files.
CoffeeScript
2
star
26

aspax-ls-handler

Plugin enabling ASPAX to handle LiveScript files.
LiveScript
2
star
27

aspax-jade-handler

Plugin enabling ASPAX to handle Jade files.
CoffeeScript
2
star
28

aspax-iced-handler

Plugin enabling ASPAX to handle IcedCoffeeScript files.
CoffeeScript
2
star
29

aspax-coffee-handler

Plugin enabling ASPAX to handle CoffeeScript files.
CoffeeScript
2
star
30

basic-stylus-grid

A very simple CSS grid system in less than 35 lines of code using Stylus
2
star
31

aspax-less-handler

Plugin enabling ASPAX to handle LESS files.
CoffeeScript
2
star
32

next-netlify-blog-starter

JavaScript
1
star
33

aspax-demo

A simple Node.js application to demonstrate the usage of ASPAX package manager.
JavaScript
1
star
34

aspax-express

Module enabling Express.js to handle assets built and packaged by ASPAX.
JavaScript
1
star
35

flutter_course

Code for Maximilian's Flutter course
Dart
1
star
36

galanta

A minimal UI library to make your Svelte apps look stylish
Svelte
1
star
37

flutterux

A Dart / Flutter / Redux project sample.
Dart
1
star
38

next8-babel-issue

A repo demonstrating issue #6273 in Next.js
JavaScript
1
star
39

mantine-datatable-v6

The table component for your Mantine data-rich applications, supporting asynchronous data loading, column sorting, custom cell data rendering, context menus, nesting, Gmail-style batch row selection, dark theme, and more.
TypeScript
1
star