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
npm run devOpen http://localhost:5173 - you should see the BrewHoard homepage.
Create Your First Account
- Click “Sign Up” in the navigation
- Enter your email and password
- You’re now logged in with an empty collection
Understanding the Codebase
Let’s explore the key directories:
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 componentsYour First Code Change
Let’s modify the homepage greeting. Open src/routes/(app)/+page.svelte:
<script>
// Find the welcome section and modify the title
</script>
<h1>Welcome to BrewHoard</h1>Change it to something custom:
<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:
<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:
// src/lib/server/db.js
import postgres from 'postgres';
const sql = postgres(process.env.DATABASE_URL);
export default sql;Querying Data
// 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
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:
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:
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:
<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:
<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:
Edit
messages/en.json:JSON{ "stats_page_title": "Collection Statistics", "stats_total_beers": "Total Beers", "stats_total_value": "Total Value" }Use in components:
Svelte<script> import * as m from '$lib/paraglide/messages'; </script> <h1>{m.stats_page_title()}</h1>Run the compiler:
Bashnpm run paraglide:compile
Running Tests
# Unit tests with Vitest
npm run test
# E2E tests with Playwright
npm run test:e2e
# Type checking with JSDoc
npm run checkCommon Development Tasks
| Task | Command |
|---|---|
| Start dev server | npm run dev |
| Run tests | npm run test |
| Lint code | npm run lint |
| Format code | npm run format |
| Build for production | npm run build |
| Preview production build | npm run preview |
| Run migrations | npm run migrate |
Next Steps
Now that you’re up and running:
- Architecture Guide - Deep dive into the codebase
- API Reference - Full API documentation
- Features Guide - Learn each feature in depth
Happy brewing!