Full example: real-time todo list
Here’s a complete example tying everything together - adapter config, Vite plugin, server actions with platform.topic(), and the reactive crud store on the client.
Open the page in two browser tabs. Create, toggle, or delete a todo in one tab → it appears in the other tab instantly.
Adapter config
svelte.config.js
import adapter from 'svelte-adapter-uws';
export default {
kit: {
adapter: adapter({
websocket: true
})
}
}; vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import uws from 'svelte-adapter-uws/vite';
export default {
plugins: [sveltekit(), uws()]
}; The Vite plugin gives you a working WebSocket server during npm run dev. Without it, WebSocket only works in production (node build).
Server actions
src/routes/todos/+page.server.js
import { db } from '$lib/server/db.js';
export async function load() {
return { todos: await db.getTodos() };
}
export const actions = {
create: async ({ request, platform }) => {
const text = (await request.formData()).get('text');
const todo = await db.createTodo(text);
platform.topic('todos').created(todo);
},
toggle: async ({ request, platform }) => {
const id = (await request.formData()).get('id');
const todo = await db.toggleTodo(id);
platform.topic('todos').updated(todo);
},
delete: async ({ request, platform }) => {
const id = (await request.formData()).get('id');
await db.deleteTodo(id);
platform.topic('todos').deleted({ id });
}
}; Each action mutates the database, then broadcasts the change to every connected client via platform.topic(). The created, updated, and deleted helpers match the events that the crud store listens for.
Client page
src/routes/todos/+page.svelte
<script>
import { crud, status } from 'svelte-adapter-uws/client';
let { data } = $props();
const todos = crud('todos', data.todos);
</script>
{#if $status === 'open'}
<span>Live</span>
{/if}
<form method="POST" action="?/create">
<input name="text" placeholder="New todo..." />
<button>Add</button>
</form>
<ul>
{#each $todos as todo (todo.id)}
<li>
<form method="POST" action="?/toggle">
<input type="hidden" name="id" value={todo.id} />
<button>{todo.done ? 'Undo' : 'Done'}</button>
</form>
<span class:done={todo.done}>{todo.text}</span>
<form method="POST" action="?/delete">
<input type="hidden" name="id" value={todo.id} />
<button>Delete</button>
</form>
</li>
{/each}
</ul> The crud store takes the topic name and the initial data from load(). It automatically handles created, updated, and deleted events - inserting, patching, or removing items by id.
How it works
- The page loads with server-rendered todos from
load(). - The
crudstore subscribes to thetodostopic over WebSocket. - When a user submits a form, SvelteKit runs the server action.
- The action writes to the database, then calls
platform.topic('todos').created(todo)(or.updated(),.deleted()). - The adapter broadcasts that event to every WebSocket client subscribed to
todos. - Each client’s
crudstore receives the event and updates its local array reactively.
No polling, no manual refetching - just forms and stores.
Was this page helpful?