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.
Browser → Server → Database → Response
- Browser Request: User navigates to a route or submits a form
- SvelteKit Route Handler: Routes determine which page/component to load
- Load Functions: Server-side data fetching and processing
- Database Queries: Efficient data retrieval using PostgreSQL
- Data Processing: Business logic and data transformation
- 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
// +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
// +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.
Server Action Example
// +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
<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
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
// $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
<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
- State Management Guide - Learn about state management patterns
- API Reference - Explore available API endpoints
- Database Schema - Understand the data structure