Skip to main content

Overview

ByteKit integrates beautifully with Svelte’s reactive system and stores. This guide shows you how to build reactive, type-safe API integrations using Svelte patterns.

Installation

npm install bytekit

Basic Setup

Simple Component Example

The simplest way to use ByteKit in Svelte:
<script lang="ts">
  import { onMount } from 'svelte';
  import { createApiClient } from 'bytekit';

  interface User {
    id: number;
    name: string;
    email: string;
    phone: string;
    website: string;
  }

  const client = createApiClient({
    baseUrl: 'https://api.example.com',
    timeoutMs: 5000,
    retryPolicy: { maxRetries: 3 }
  });

  let data: User | null = null;
  let loading = true;
  let error: string | null = null;

  onMount(async () => {
    try {
      data = await client.get('/users/1') as User;
    } catch (err) {
      error = err instanceof Error ? err.message : 'Unknown error';
    } finally {
      loading = false;
    }
  });
</script>

<div class="container">
  <h1>ByteKit + Svelte</h1>
  
  <div class="example">
    <h2>API Client Example</h2>
    
    {#if loading}
      <p>Loading...</p>
    {:else if error}
      <p class="error">Error: {error}</p>
    {:else if data}
      <pre class="result">{JSON.stringify(data, null, 2)}</pre>
    {/if}
  </div>

  <div class="features">
    <p>✅ Svelte reactive stores</p>
    <p>✅ Simple and clean API</p>
    <p>✅ TypeScript ready</p>
  </div>
</div>

<style>
  .container {
    padding: 2rem;
    font-family: system-ui;
  }

  .example {
    margin-top: 2rem;
  }

  .error {
    color: red;
  }

  .result {
    background: #f5f5f5;
    padding: 1rem;
    border-radius: 8px;
    overflow: auto;
  }

  .features {
    margin-top: 2rem;
    font-size: 0.9rem;
    color: #666;
  }
</style>

Using Svelte Stores

Create reusable stores for API state management:
// stores/api.ts
import { writable } from "svelte/store";
import { createApiClient } from "bytekit";

export function createApiStore(config: Parameters<typeof createApiClient>[0]) {
    const client = createApiClient(config);
    return client;
}

interface QueryState<T> {
    data: T | null;
    loading: boolean;
    error: string | null;
}

export function createQueryStore<T>(
    client: ReturnType<typeof createApiClient>,
    url: string
) {
    const { subscribe, set, update } = writable<QueryState<T>>({
        data: null,
        loading: true,
        error: null,
    });

    async function fetch() {
        update((state) => ({ ...state, loading: true }));

        try {
            const data = await client.get(url);
            set({ data: data as T, loading: false, error: null });
        } catch (err) {
            set({
                data: null,
                loading: false,
                error: err instanceof Error ? err.message : "Unknown error",
            });
        }
    }

    fetch();

    return {
        subscribe,
        refetch: fetch,
    };
}

Usage with Stores

<script lang="ts">
  import { createApiStore, createQueryStore } from './stores/api';

  interface User {
    id: number;
    name: string;
    email: string;
  }

  const apiClient = createApiStore({
    baseUrl: 'https://api.example.com',
    timeoutMs: 5000,
  });

  const userStore = createQueryStore<User>(apiClient, '/users/1');
</script>

<div>
  {#if $userStore.loading}
    <p>Loading...</p>
  {:else if $userStore.error}
    <p class="error">{$userStore.error}</p>
  {:else if $userStore.data}
    <div>
      <h2>{$userStore.data.name}</h2>
      <p>{$userStore.data.email}</p>
      <button on:click={() => userStore.refetch()}>Refresh</button>
    </div>
  {/if}
</div>

Using QueryClient

Integrate ByteKit’s QueryClient for advanced caching:
// stores/queryClient.ts
import { writable } from "svelte/store";
import { createApiClient, createQueryClient } from "bytekit";

const apiClient = createApiClient({
    baseUrl: "https://api.example.com",
});

export const queryClient = createQueryClient(apiClient, {
    defaultStaleTime: 5000,
    defaultCacheTime: 60000,
    globalCallbacks: {
        onStart: (context) => {
            console.log(`[Query] ${context.method} ${context.url}`);
        },
    },
});

interface QueryState<T> {
    data: T | null;
    loading: boolean;
    error: Error | null;
}

export function createQuery<T>(queryKey: string[], path: string) {
    const store = writable<QueryState<T>>({
        data: null,
        loading: true,
        error: null,
    });

    async function fetch() {
        store.update((s) => ({ ...s, loading: true }));

        try {
            const result = await queryClient.query({ queryKey, path });
            store.set({ data: result as T, loading: false, error: null });
        } catch (err) {
            store.set({
                data: null,
                loading: false,
                error: err as Error,
            });
        }
    }

    fetch();

    return {
        subscribe: store.subscribe,
        refetch: fetch,
    };
}

Usage Example

<script lang="ts">
  import { createQuery } from './stores/queryClient';

  interface User {
    id: number;
    name: string;
    email: string;
  }

  export let userId: string;

  const query = createQuery<User>(['user', userId], `/users/${userId}`);
</script>

<div>
  {#if $query.loading}
    <p>Loading...</p>
  {:else if $query.error}
    <p>Error: {$query.error.message}</p>
  {:else if $query.data}
    <div>
      <h2>{$query.data.name}</h2>
      <p>{$query.data.email}</p>
      <button on:click={query.refetch}>Refresh</button>
    </div>
  {/if}
</div>

Mutations

Handle POST, PUT, DELETE operations:
// stores/mutations.ts
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";
import { createApiClient } from "bytekit";

interface MutationState<T> {
    data: T | null;
    loading: boolean;
    error: string | null;
}

export function createMutation<T, V>(
    client: ReturnType<typeof createApiClient>
) {
    const store: Writable<MutationState<T>> = writable({
        data: null,
        loading: false,
        error: null,
    });

    const mutate = async (url: string, body: V, method = "POST") => {
        store.set({ data: null, loading: true, error: null });

        try {
            const response = await client.request({
                url,
                method,
                body,
            });
            store.set({ data: response as T, loading: false, error: null });
            return response as T;
        } catch (err) {
            const error = err instanceof Error ? err.message : "Unknown error";
            store.set({ data: null, loading: false, error });
            throw err;
        }
    };

    return {
        subscribe: store.subscribe,
        mutate,
    };
}

Usage Example

<script lang="ts">
  import { createApiStore } from './stores/api';
  import { createMutation } from './stores/mutations';

  interface User {
    id: number;
    name: string;
    email: string;
  }

  const client = createApiStore({
    baseUrl: 'https://api.example.com',
  });

  const mutation = createMutation<User, Partial<User>>(client);

  let formData = {
    name: '',
    email: '',
  };

  async function handleSubmit() {
    try {
      const newUser = await mutation.mutate('/users', formData);
      console.log('Created:', newUser);
      formData = { name: '', email: '' };
    } catch (err) {
      // Error is already in state
    }
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <input bind:value={formData.name} placeholder="Name" />
  <input bind:value={formData.email} placeholder="Email" />
  
  {#if $mutation.error}
    <p class="error">{$mutation.error}</p>
  {/if}
  
  <button disabled={$mutation.loading}>
    {$mutation.loading ? 'Creating...' : 'Create User'}
  </button>
</form>

Reactive Patterns

Auto-refetch on Prop Changes

<script lang="ts">
  import { createApiStore } from './stores/api';

  export let userId: string;

  const client = createApiStore({
    baseUrl: 'https://api.example.com',
  });

  let data = null;
  let loading = true;
  let error = null;

  $: {
    loading = true;
    client.get(`/users/${userId}`)
      .then(result => {
        data = result;
        loading = false;
      })
      .catch(err => {
        error = err.message;
        loading = false;
      });
  }
</script>

<div>
  {#if loading}
    <p>Loading user {userId}...</p>
  {:else if error}
    <p>Error: {error}</p>
  {:else if data}
    <pre>{JSON.stringify(data, null, 2)}</pre>
  {/if}
</div>

Best Practices

Create stores for API clients that need to be shared across components:
// stores/api.ts
export const apiClient = createApiClient({
    baseUrl: 'https://api.example.com',
});
Use Svelte’s reactive statements ($:) for automatic refetching:
<script>
  export let userId;
  $: userPromise = client.get(`/users/${userId}`);
</script>

{#await userPromise}
  <p>Loading...</p>
{:then user}
  <p>{user.name}</p>
{:catch error}
  <p>Error: {error.message}</p>
{/await}
Use TypeScript for type-safe API responses:
const data = await client.get('/users/1') as User;
// data is typed as User
Stores automatically cleanup subscriptions, but cleanup async operations:
onMount(() => {
    let cancelled = false;
    
    fetchData().then(data => {
        if (!cancelled) {
            // update state
        }
    });
    
    return () => {
        cancelled = true;
    };
});

Next Steps

API Client Guide

Learn more about ApiClient configuration

State Management

Explore QueryClient for advanced caching

TypeScript Support

Generate types from OpenAPI specs

Error Handling

Best practices for error handling

Build docs developers (and LLMs) love