[Svelte Series 11] API Integration and Optimistic UI Flow

한국어 버전

You already have the form. Now wire it to the real server while keeping interactions snappy.

Quick recap

  • What we're building: the /projects page that loads the project list from the server, creates entries, and toggles stars optimistically.
  • Why it matters: once you understand the load/invalidate pairing you can sync server state without hard reloads, and optimistic UI keeps perceived speed high on slow networks.
  • Watch out for: line up depends/invalidate keys precisely, and capture snapshots before optimistic updates so failures can roll back instantly.

Key terms

  1. load function: the SvelteKit server hook that runs before the page renders and fetches the initial data.
  2. invalidate: reruns a load result for a specific key so the UI refreshes without reloading the full page.
  3. Optimistic UI: renders the expected result before the server confirms it to boost perceived responsiveness.
  4. Snapshot rollback: cloning the list before an optimistic update so you can revert immediately on failure.

Core ideas

An API (application programming interface) is a contract for exchanging data with the server. The load function calls the API before rendering so the page has data ready. invalidate asks SvelteKit to rerun a load tied to a key. Optimistic UI paints the expected result first; to keep it safe you must hold a snapshot and restore it if the request fails.

Server vs client boundary

  • +page.server.ts hosts load and actions; they only run on the server and can touch the database or secrets.
  • +page.svelte renders in the browser, receives data from the server, and can call invalidate to rerun the server load.
  • The optimistic toggle updates the browser store (projectsStore) first. If the server rejects the change, it restores the snapshot. Remember you are intentionally doing “show first, confirm later.”

Data flow (D2)

Browser formSvelteKit form action/api/projects RESTPrisma/Database+page.server loadwritable(projectsStore)Project list UI submitfetch POST /api/projectsinsertresult JSON201 responseinvalidate('data:projects')fetch GET /api/projectsdata.projectssubscribefetch POST /api/projects/:id/staroptimistic toggleerror => rollback snapshot

👉 Mini recap: the browser form triggers the action, the action talks to the API, and on success it calls invalidate so the list refreshes without a full reload. The star toggle stores a snapshot so it can revert instantly when the server returns an error.

Code examples

The list fetches projects, creating entries refreshes the list, and the star toggle stays optimistic.

// src/routes/projects/+page.server.ts

export const load = async ({ fetch, depends }) => {
  depends('data:projects');
  const res = await fetch('/api/projects');
  if (!res.ok) {
    return { projects: [], error: 'Failed to load projects.' };
  }
  return { projects: await res.json() };
};

export const actions = {
  create: async ({ request, fetch }) => {
    const form = Object.fromEntries(await request.formData());
    if (!form.title) {
      return fail(400, { errors: { title: 'Title is required.' } });
    }
    const res = await fetch('/api/projects', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(form)
    });
    if (!res.ok) {
      return fail(500, { errors: { title: 'Failed to create project.' } });
    }
    return { created: true };
  }
};
<!-- src/routes/projects/+page.svelte -->
<script>
  import { enhance } from '$app/forms';
  import { invalidate } from '$app/navigation';
  import { page } from '$app/stores';
  import { derived, writable, get } from 'svelte/store';
  export let data;

  const projectsStore = writable(data.projects);
  const pending = derived(page, ($page) => $page.form?.errors);

  async function toggleStar(id) {
    const snapshot = get(projectsStore);
    projectsStore.update((list) => list.map((p) => (p.id === id ? { ...p, starred: !p.starred } : p)));
    const res = await fetch(`/api/projects/${id}/star`, { method: 'POST' });
    if (!res.ok) projectsStore.set(snapshot);
  }
</script>

<form method="POST" action="?/create"
  use:enhance={({ result }) => {
    if (result.type === 'success') invalidate('data:projects');
  }}>
  <label>
    <span class="sr-only">Project name</span>
    <input name="title" placeholder="Project name" aria-invalid={pending?.title ? 'true' : 'false'} required />
  </label>
  {#if pending?.title}
    <p class="form-error">{pending.title}</p>
  {/if}
  <button type="submit">Add</button>
</form>

<ul>
  {#each $projectsStore as project (project.id)}
    <li>
      <button type="button" on:click={() => toggleStar(project.id)} aria-pressed={project.starred}>
        {project.starred ? '★' : '☆'}
      </button>
      {project.title}
    </li>
  {/each}
</ul>
http://localhost:5173/projects
DEV

UI Preview

New items and stars show up instantly

Submitting the form adds a project without flicker. Star toggles apply immediately and roll back if the server errors.

load+invalidateoptimistic

List

Project Orbit

Star button toggles optimistically.

Create

POST /api/projects

Invalidate ('data:projects') after success.

Optimistic

Snapshot rollback

Restore the previous array when the server fails.

👉 Mini recap: the three tabs share the same data source. The create form triggers invalidate, and the list toggles stars optimistically but restores the saved array when the API errors.

depends('data:projects') pairs with invalidate('data:projects') so new items refresh the list without reloading the entire page. Meanwhile the optimistic update makes sure the star toggle feels instant.

Why it matters

School dashboards often suffer from slow networks, prompting repeated clicks. Optimistic UI shows results immediately, calming users and rolling back only when necessary. Once you understand invalidate, you can keep multiple collaborators in sync without forcing a refresh.

Practice tasks

  • Follow along: hook up load and invalidate('data:projects') so the list refreshes without a full reload.
  • Extend it: add optimistic updates like toggleStar and show a toast when the request fails.
  • Debugging: if the data never refreshes, confirm the depends key matches the invalidate key.
  • Done when: you can demo creating items, toggling stars, and manually refreshing within a minute while failed toggles revert correctly.

Wrap-up

API integration plus optimistic UI balances speed and trust. Next time we'll embed the pattern into a mini dashboard so multiple components share the flow on a single screen.

💬 댓글

이 글에 대한 의견을 남겨주세요