Data Flow

Detailed guide on request lifecycle and data flow in BrewHoard

BrewHoard uses SvelteKit’s powerful data loading and form handling patterns to create a seamless user experience. This guide explains how data flows through the application from user interactions to database updates and back to the UI.

Request Lifecycle

Every interaction in BrewHoard follows a predictable lifecycle that ensures data consistency and optimal performance.

Loading diagram...

Browser → Server → Database → Response

  1. Browser Request: User navigates to a route or submits a form
  2. SvelteKit Route Handler: Routes determine which page/component to load
  3. Load Functions: Server-side data fetching and processing
  4. Database Queries: Efficient data retrieval using PostgreSQL
  5. Data Processing: Business logic and data transformation
  6. Response: Hydrated page sent to browser

SvelteKit Load Functions

Load functions are the backbone of data fetching in BrewHoard. They run on the server and provide data to pages and components.

Page Load Function Example

JavaScript
// +page.server.js
import { getBeers } from '$lib/collection/collection.js';

/** @type {import('./$types').PageServerLoad} */
export async function load({ url, locals }) {
  const userId = locals.user.id;
  const searchParams = url.searchParams;

  // Parse query parameters
  const filters = {
    style: searchParams.get('style'),
    rating: searchParams.get('rating'),
    search: searchParams.get('q')
  };

  // Fetch data with filters
  const beers = await getBeers(userId, filters);

  return {
    beers,
    filters,
    totalCount: beers.length
  };
}

Component Load Function

JavaScript
// +page.js (client-side)
import { getBeerDetails } from '$lib/beer/beer-search.js';

/** @type {import('./$types').PageLoad} */
export async function load({ params, fetch }) {
  const beerId = params.id;

  // Client-side data fetching
  const beer = await getBeerDetails(beerId, { fetch });

  return {
    beer
  };
}

Form Actions and Progressive Enhancement

BrewHoard uses SvelteKit’s form actions for robust form handling that works with and without JavaScript.

Loading diagram...

Server Action Example

JavaScript
// +page.server.js
import { addBeer } from '$lib/collection/collection.js';
import { fail } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
  addBeer: async ({ request, locals }) => {
    const userId = locals.user.id;
    const data = await request.formData();

    // Validate form data
    const beerData = {
      name: data.get('name'),
      brewery: data.get('brewery'),
      style: data.get('style'),
      abv: parseFloat(data.get('abv'))
    };

    // Server-side validation
    if (!beerData.name || !beerData.brewery) {
      return fail(400, {
        error: 'Name and brewery are required',
        data: beerData
      });
    }

    try {
      await addBeer(userId, beerData);
      return { success: true };
    } catch (error) {
      return fail(500, { error: 'Failed to add beer' });
    }
  }
};

Form Component with Progressive Enhancement

Svelte
<script>
  import { enhance } from '$app/forms';
  import { invalidateAll } from '$app/navigation';

  /** @type {import('./$types').ActionData} */
  export let form;
</script>

<form method="POST" action="?/addBeer" use:enhance={() => {
  return async ({ update, result }) => {
    // Custom enhancement logic
    await update({ reset: false });

    if (result.type === 'success') {
      // Invalidate data to trigger re-fetch
      await invalidateAll();
      // Reset form
      // ... form reset logic
    }
  };
}}>
  <input name="name" placeholder="Beer name" required />
  <input name="brewery" placeholder="Brewery" required />
  <input name="style" placeholder="Style" />
  <input type="number" name="abv" placeholder="ABV %" step="0.1" />

  {#if form?.error}
    <p class="error">{form.error}</p>
  {/if}

  <button type="submit">Add Beer</button>
</form>

Client-side Navigation and Data Invalidation

SvelteKit’s client-side navigation ensures fast transitions while maintaining data freshness.

Programmatic Navigation with Data Invalidation

JavaScript
import { goto } from '$app/navigation';
import { invalidate } from '$app/navigation';

// Navigate and invalidate specific data
async function navigateToCollection() {
  await goto('/collection');
  // Invalidate collection data
  await invalidate((url) => url.pathname === '/collection');
}

// Invalidate multiple routes
await invalidate([
  (url) => url.pathname.startsWith('/collection'),
  (url) => url.pathname === '/dashboard'
]);

Real-time Updates Patterns

For features requiring real-time updates, BrewHoard implements polling and WebSocket patterns.

Polling with Stores

JavaScript
// $lib/stores/marketplace.js
import { writable } from 'svelte/store';
import { browser } from '$app/environment';

export const marketplaceListings = writable([]);

if (browser) {
  // Poll for updates every 30 seconds
  const pollInterval = setInterval(async () => {
    try {
      const response = await fetch('/api/marketplace/listings');
      const listings = await response.json();
      marketplaceListings.set(listings);
    } catch (error) {
      console.error('Failed to fetch marketplace listings:', error);
    }
  }, 30000);

  // Cleanup on page unload
  window.addEventListener('beforeunload', () => {
    clearInterval(pollInterval);
  });
}

Optimistic Updates

Svelte
<script>
  import { enhance } from '$app/forms';
  import { invalidateAll } from '$app/navigation';

  let rating = 0;

  // Optimistic update
  function optimisticRate(newRating) {
    rating = newRating;
    // Show immediate feedback
  }
</script>

<form method="POST" action="?/rate" use:enhance={() => {
  return async ({ update, result }) => {
    if (result.type === 'success') {
      await invalidateAll();
    } else {
      // Revert optimistic update on failure
      rating = 0;
    }
  };
}}>
  <input type="hidden" name="rating" value={rating} />
  <button on:click={() => optimisticRate(5)}>⭐⭐⭐⭐⭐</button>
</form>

Next Steps