Quick Start

Get BrewHoard running and make your first changes in under 5 minutes.

This guide assumes you’ve completed the Installation . Let’s build something!

Start the Dev Server

Bash
npm run dev

Open http://localhost:5173 - you should see the BrewHoard homepage.

Create Your First Account

  1. Click “Sign Up” in the navigation
  2. Enter your email and password
  3. You’re now logged in with an empty collection

Understanding the Codebase

Let’s explore the key directories:

Text
src/
├── routes/           # SvelteKit pages and API endpoints
│   ├── (app)/        # Authenticated app routes
│   │   ├── collection/   # Collection management
│   │   ├── marketplace/  # Trading features
│   │   └── scan/         # Beer scanner
│   └── api/          # REST API endpoints
│       └── v1/       # Versioned API
├── lib/              # Shared utilities and components
│   ├── auth/         # Authentication logic
│   ├── beer/         # Beer-related functions
│   ├── collection/   # Collection management
│   └── server/       # Server-only code (db.js)
└── components/       # Reusable UI components

Your First Code Change

Let’s modify the homepage greeting. Open src/routes/(app)/+page.svelte:

Svelte
<script>
  // Find the welcome section and modify the title
</script>

<h1>Welcome to BrewHoard</h1>

Change it to something custom:

Svelte
<h1>My Awesome Beer Collection</h1>

Save the file - the browser updates instantly via HMR (Hot Module Replacement).

Adding a New Page

Create a new route at src/routes/(app)/stats/+page.svelte:

Svelte
<script>
  import * as m from '$lib/paraglide/messages';
  
  let totalBeers = $state(0);
  let totalValue = $state(0);
  
  $effect(() => {
    // Fetch stats when component mounts
    fetch('/api/v1/collection')
      .then(r => r.json())
      .then(data => {
        totalBeers = data.items?.length ?? 0;
        totalValue = data.items?.reduce((sum, item) => 
          sum + (item.purchase_price || 0), 0) ?? 0;
      });
  });
</script>

<div class="p-6">
  <h1 class="text-2xl font-bold mb-4">Collection Statistics</h1>
  
  <div class="grid grid-cols-2 gap-4">
    <div class="bg-amber-50 p-4 rounded-lg">
      <p class="text-sm text-amber-600">Total Beers</p>
      <p class="text-3xl font-bold">{totalBeers}</p>
    </div>
    
    <div class="bg-green-50 p-4 rounded-lg">
      <p class="text-sm text-green-600">Total Value</p>
      <p class="text-3xl font-bold">${totalValue.toFixed(2)}</p>
    </div>
  </div>
</div>

Navigate to http://localhost:5173/stats to see your new page!

Working with the Database

The database is accessed via porsager/postgres. Here’s a common pattern:

JavaScript
// src/lib/server/db.js
import postgres from 'postgres';

const sql = postgres(process.env.DATABASE_URL);

export default sql;

Querying Data

JavaScript
// In a +page.server.js or +server.js
import sql from '$lib/server/db.js';

export async function load({ locals }) {
  const beers = await sql`
    SELECT b.*, br.name as brewery_name
    FROM beers b
    LEFT JOIN breweries br ON b.brewery_id = br.id
    WHERE b.is_active = true
    ORDER BY b.name
    LIMIT 50
  `;
  
  return { beers };
}

Inserting Data

JavaScript
const [newBeer] = await sql`
  INSERT INTO beers (name, style, abv, brewery_id)
  VALUES (${name}, ${style}, ${abv}, ${breweryId})
  RETURNING *
`;

Creating an API Endpoint

Add a new endpoint at src/routes/api/v1/stats/+server.js:

JavaScript
import { json } from '@sveltejs/kit';
import sql from '$lib/server/db.js';

/** @type {import('./$types').RequestHandler} */
export async function GET({ locals }) {
  // Check authentication
  if (!locals.user) {
    return json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  const [stats] = await sql`
    SELECT 
      COUNT(*) as total_items,
      SUM(quantity) as total_bottles,
      SUM(purchase_price * quantity) as total_value
    FROM user_collection
    WHERE user_id = ${locals.user.id}
  `;
  
  return json(stats);
}

Test it with curl:

Bash
curl http://localhost:5173/api/v1/stats 
  -H "Cookie: session=your-session-cookie"

Working with Components

BrewHoard uses shadcn-svelte. Import and use components like this:

Svelte
<script>
  import { Button } from '$lib/components/ui/button';
  import { Card, CardHeader, CardTitle, CardContent } from '$lib/components/ui/card';
</script>

<Card>
  <CardHeader>
    <CardTitle>My Beer</CardTitle>
  </CardHeader>
  <CardContent>
    <p>This is a delicious IPA.</p>
    <Button variant="default">Add to Collection</Button>
  </CardContent>
</Card>

Using Svelte 5 Runes

BrewHoard uses Svelte 5’s rune syntax:

Svelte
<script>
  // Reactive state
  let count = $state(0);
  
  // Derived values
  let doubled = $derived(count * 2);
  
  // Side effects
  $effect(() => {
    console.log(`Count changed to ${count}`);
  });
  
  // Props
  let { title, onSave } = $props();
</script>

<button onclick={() => count++}>
  Clicked {count} times (doubled: {doubled})
</button>

Adding Translations

BrewHoard uses Paraglide for i18n. Add new messages:

  1. Edit messages/en.json:

    JSON
    {
      "stats_page_title": "Collection Statistics",
      "stats_total_beers": "Total Beers",
      "stats_total_value": "Total Value"
    }
  2. Use in components:

    Svelte
    <script>
      import * as m from '$lib/paraglide/messages';
    </script>
    
    <h1>{m.stats_page_title()}</h1>
  3. Run the compiler:

    Bash
    npm run paraglide:compile

Running Tests

Bash
# Unit tests with Vitest
npm run test

# E2E tests with Playwright
npm run test:e2e

# Type checking with JSDoc
npm run check

Common Development Tasks

TaskCommand
Start dev servernpm run dev
Run testsnpm run test
Lint codenpm run lint
Format codenpm run format
Build for productionnpm run build
Preview production buildnpm run preview
Run migrationsnpm run migrate

Next Steps

Now that you’re up and running:

  1. Architecture Guide - Deep dive into the codebase
  2. API Reference - Full API documentation
  3. Features Guide - Learn each feature in depth

Happy brewing!