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