@icarusmx/creta 1.5.11 → 1.5.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/creta.js +37 -1
- package/lib/data/command-help/aws-ec2.js +34 -0
- package/lib/data/command-help/grep.js +72 -0
- package/lib/data/command-help/index.js +9 -1
- package/lib/executors/CommandHelpExecutor.js +6 -1
- package/lib/executors/ExercisesExecutor.js +8 -0
- package/lib/exercises/.claude/settings.local.json +12 -0
- package/lib/exercises/01-developing-muscle-for-nvim.md +528 -0
- package/lib/exercises/{iterm2-pane-navigation.md → 02-iterm2-pane-navigation.md} +1 -1
- package/lib/exercises/05-svelte-first-steps.md +1340 -0
- package/lib/exercises/{curl-and-pipes.md → 06-curl-and-pipes.md} +187 -72
- package/lib/exercises/07-claude-api-first-steps.md +855 -0
- package/lib/exercises/08-playwright-svelte-guide.md +1384 -0
- package/lib/exercises/09-docker-first-steps.md +1475 -0
- package/lib/exercises/{railway-deployment.md → 10-railway-deployment.md} +1 -0
- package/lib/exercises/{aws-billing-detective.md → 11-aws-billing-detective.md} +215 -35
- package/lib/exercises/12-install-skills.md +755 -0
- package/lib/exercises/README.md +180 -0
- package/lib/exercises/utils/booklet-2up.js +133 -0
- package/lib/exercises/utils/booklet-manual-duplex.js +159 -0
- package/lib/exercises/utils/booklet-simple.js +136 -0
- package/lib/exercises/utils/create-booklet.js +116 -0
- package/lib/scripts/aws-ec2-all.sh +58 -0
- package/package.json +3 -2
- /package/lib/exercises/{git-stash-workflow.md → 03-git-stash-workflow.md} +0 -0
- /package/lib/exercises/{array-object-manipulation.md → 04-array-object-manipulation.md} +0 -0
|
@@ -0,0 +1,1340 @@
|
|
|
1
|
+
# SvelteKit First Steps - Project Structure Without the Confusion
|
|
2
|
+
|
|
3
|
+
<!-- vim: set foldmethod=marker foldlevel=0: -->
|
|
4
|
+
|
|
5
|
+
## 📖 LazyVim Reading Guide {{{
|
|
6
|
+
|
|
7
|
+
**Start with:** `zM` (close all folds) → Navigate with `za` (toggle fold under cursor)
|
|
8
|
+
|
|
9
|
+
This document uses fold markers `{{{` and `}}}` for organized reading.
|
|
10
|
+
|
|
11
|
+
}}}
|
|
12
|
+
|
|
13
|
+
## 🚨 Problem: "Where Does This File Go?" {{{
|
|
14
|
+
|
|
15
|
+
You're building a SvelteKit app and constantly wondering:
|
|
16
|
+
- **"Should this component go in `src/lib` or `src/routes`?"**
|
|
17
|
+
- **"What's the difference between a `.svelte` file and a `+page.svelte` file?"**
|
|
18
|
+
- **"Do I need to write something in `index.js`?"**
|
|
19
|
+
- **"Where do API endpoints go?"**
|
|
20
|
+
|
|
21
|
+
**Common confusion:**
|
|
22
|
+
- Components scattered between routes and lib
|
|
23
|
+
- Not understanding the `$lib` alias
|
|
24
|
+
- Creating `+server.js` files in the wrong place
|
|
25
|
+
- Overengineering with unnecessary barrel exports
|
|
26
|
+
|
|
27
|
+
**The root cause:** SvelteKit has special file naming conventions that aren't immediately obvious.
|
|
28
|
+
|
|
29
|
+
}}}
|
|
30
|
+
|
|
31
|
+
## ✅ Solution: Understanding SvelteKit's File Structure {{{
|
|
32
|
+
|
|
33
|
+
SvelteKit uses **file-based routing** with special file names that have specific meanings.
|
|
34
|
+
|
|
35
|
+
**Think of it like this:**
|
|
36
|
+
- **`src/routes/`** = Your website's pages and API endpoints (what users can visit)
|
|
37
|
+
- **`src/lib/`** = Your shared toolbox (reusable code that powers your pages)
|
|
38
|
+
|
|
39
|
+
**What SvelteKit gives you:**
|
|
40
|
+
- 📁 **Clear separation** - Routes vs reusable code
|
|
41
|
+
- 🔗 **$lib alias** - Import from anywhere with `$lib/...`
|
|
42
|
+
- 🎯 **Convention over configuration** - Special file names do special things
|
|
43
|
+
- ⚡ **Automatic routing** - File structure = URL structure
|
|
44
|
+
|
|
45
|
+
**Core concept:** The `src/routes/` folder structure directly maps to your URLs, while `src/lib/` is your private code library.
|
|
46
|
+
|
|
47
|
+
}}}
|
|
48
|
+
|
|
49
|
+
## 🎯 Core Concepts {{{
|
|
50
|
+
|
|
51
|
+
### src/routes/ - Your Website Structure {{{
|
|
52
|
+
|
|
53
|
+
**What lives here:**
|
|
54
|
+
- Pages users can visit (`+page.svelte`)
|
|
55
|
+
- Layouts that wrap pages (`+layout.svelte`)
|
|
56
|
+
- API endpoints (`+server.js`)
|
|
57
|
+
- Server-side logic (`+page.server.js`, `+layout.server.js`)
|
|
58
|
+
|
|
59
|
+
**File structure = URL structure:**
|
|
60
|
+
```
|
|
61
|
+
src/routes/
|
|
62
|
+
├── +page.svelte → /
|
|
63
|
+
├── about/
|
|
64
|
+
│ └── +page.svelte → /about
|
|
65
|
+
├── blog/
|
|
66
|
+
│ ├── +page.svelte → /blog
|
|
67
|
+
│ └── [slug]/
|
|
68
|
+
│ └── +page.svelte → /blog/my-post
|
|
69
|
+
└── api/
|
|
70
|
+
├── contact/
|
|
71
|
+
│ └── +server.js → /api/contact (POST, GET, etc.)
|
|
72
|
+
└── users/
|
|
73
|
+
└── +server.js → /api/users
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Special files in routes:**
|
|
77
|
+
- `+page.svelte` - A page component
|
|
78
|
+
- `+layout.svelte` - Wraps multiple pages
|
|
79
|
+
- `+server.js` - API endpoint (handles HTTP requests)
|
|
80
|
+
- `+page.server.js` - Server-side page logic
|
|
81
|
+
- `+error.svelte` - Error page
|
|
82
|
+
|
|
83
|
+
**Example URL mapping:**
|
|
84
|
+
```bash
|
|
85
|
+
src/routes/products/+page.svelte → https://yoursite.com/products
|
|
86
|
+
src/routes/products/[id]/+page.svelte → https://yoursite.com/products/123
|
|
87
|
+
src/routes/api/products/+server.js → https://yoursite.com/api/products
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
}}}
|
|
91
|
+
|
|
92
|
+
### src/lib/ - Your Code Toolbox {{{
|
|
93
|
+
|
|
94
|
+
**What lives here:**
|
|
95
|
+
- Reusable components (`Footer.svelte`, `Navbar.svelte`)
|
|
96
|
+
- Utility functions (`formatDate.js`, `validation.js`)
|
|
97
|
+
- API client code (functions that CALL external APIs)
|
|
98
|
+
- Shared constants and configs
|
|
99
|
+
- Database query functions
|
|
100
|
+
- Any code used by multiple routes
|
|
101
|
+
|
|
102
|
+
**Common structure:**
|
|
103
|
+
```
|
|
104
|
+
src/lib/
|
|
105
|
+
├── components/
|
|
106
|
+
│ ├── Footer.svelte
|
|
107
|
+
│ ├── Navbar.svelte
|
|
108
|
+
│ └── Button.svelte
|
|
109
|
+
├── utils/
|
|
110
|
+
│ ├── formatDate.js
|
|
111
|
+
│ └── validation.js
|
|
112
|
+
├── api/
|
|
113
|
+
│ └── emailClient.js # Functions that call external APIs
|
|
114
|
+
├── db/
|
|
115
|
+
│ └── queries.js # Database helper functions
|
|
116
|
+
└── index.js # Optional barrel exports
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**The `$lib` alias:**
|
|
120
|
+
```javascript
|
|
121
|
+
// Import from anywhere in your app
|
|
122
|
+
import Footer from '$lib/components/Footer.svelte';
|
|
123
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
124
|
+
import { sendEmail } from '$lib/api/emailClient.js';
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Key insight:** `$lib` is just an alias for `src/lib/`. You can import from ANY file inside `src/lib/` directly using this alias.
|
|
128
|
+
|
|
129
|
+
}}}
|
|
130
|
+
|
|
131
|
+
### The Optional index.js {{{
|
|
132
|
+
|
|
133
|
+
**What it does:**
|
|
134
|
+
`src/lib/index.js` is an **optional convenience file** for barrel exports.
|
|
135
|
+
|
|
136
|
+
**Without index.js (direct imports):**
|
|
137
|
+
```javascript
|
|
138
|
+
import Footer from '$lib/components/Footer.svelte';
|
|
139
|
+
import Navbar from '$lib/components/Navbar.svelte';
|
|
140
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**With index.js (barrel exports):**
|
|
144
|
+
```javascript
|
|
145
|
+
// src/lib/index.js
|
|
146
|
+
export { default as Footer } from './components/Footer.svelte';
|
|
147
|
+
export { default as Navbar } from './components/Navbar.svelte';
|
|
148
|
+
export { formatDate } from './utils/formatDate.js';
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then you can do:
|
|
152
|
+
```javascript
|
|
153
|
+
import { Footer, Navbar, formatDate } from '$lib';
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**When to use it:**
|
|
157
|
+
- ✅ **Use barrel exports** if you have many related exports and want a cleaner import
|
|
158
|
+
- ❌ **Skip it** for simple projects where direct imports are clearer (preferred for most cases)
|
|
159
|
+
|
|
160
|
+
**Pro tip:** For simple projects, direct imports are more explicit and easier to trace. Don't overengineer with barrel exports unless you have a real need.
|
|
161
|
+
|
|
162
|
+
}}}
|
|
163
|
+
|
|
164
|
+
### +server.js - API Endpoints {{{
|
|
165
|
+
|
|
166
|
+
**IMPORTANT:** `+server.js` files **ONLY** go in `src/routes/`
|
|
167
|
+
|
|
168
|
+
**What they do:**
|
|
169
|
+
Create API endpoints your app exposes to the world.
|
|
170
|
+
|
|
171
|
+
**Example - Contact Form API:**
|
|
172
|
+
```javascript
|
|
173
|
+
// src/routes/api/contact/+server.js
|
|
174
|
+
export async function POST({ request }) {
|
|
175
|
+
const data = await request.json();
|
|
176
|
+
|
|
177
|
+
// Process the contact form
|
|
178
|
+
await sendEmail(data.email, data.message);
|
|
179
|
+
|
|
180
|
+
return new Response(
|
|
181
|
+
JSON.stringify({ success: true }),
|
|
182
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This creates: `https://yoursite.com/api/contact` (POST)
|
|
188
|
+
|
|
189
|
+
**HTTP methods:**
|
|
190
|
+
```javascript
|
|
191
|
+
// src/routes/api/users/+server.js
|
|
192
|
+
export async function GET() {
|
|
193
|
+
return new Response(JSON.stringify({ users: [...] }));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function POST({ request }) {
|
|
197
|
+
// Create new user
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function PUT({ request }) {
|
|
201
|
+
// Update user
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function DELETE({ request }) {
|
|
205
|
+
// Delete user
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Key distinction:**
|
|
210
|
+
- `src/routes/api/**/+server.js` → API endpoints your app **exposes**
|
|
211
|
+
- `src/lib/api/client.js` → Functions that **call** external APIs (helper code)
|
|
212
|
+
|
|
213
|
+
**You NEVER write `+server.js` files in `src/lib/`** - that's not how it works!
|
|
214
|
+
|
|
215
|
+
}}}
|
|
216
|
+
|
|
217
|
+
}}}
|
|
218
|
+
|
|
219
|
+
## 🏗️ Setting Up Your First SvelteKit Project {{{
|
|
220
|
+
|
|
221
|
+
### Step 1: Create a New Project {{{
|
|
222
|
+
|
|
223
|
+
**Using create-svelte:**
|
|
224
|
+
```bash
|
|
225
|
+
# Create new project
|
|
226
|
+
npm create svelte@latest my-app
|
|
227
|
+
|
|
228
|
+
# Choose options:
|
|
229
|
+
# - SvelteKit demo app (for learning) or Skeleton project
|
|
230
|
+
# - No TypeScript (if you prefer JavaScript)
|
|
231
|
+
# - Add ESLint and Prettier
|
|
232
|
+
|
|
233
|
+
# Navigate and install
|
|
234
|
+
cd my-app
|
|
235
|
+
npm install
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Start dev server:**
|
|
239
|
+
```bash
|
|
240
|
+
npm run dev -- --open
|
|
241
|
+
# Server runs on http://localhost:5173
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Initial structure:**
|
|
245
|
+
```
|
|
246
|
+
my-app/
|
|
247
|
+
├── src/
|
|
248
|
+
│ ├── routes/
|
|
249
|
+
│ │ └── +page.svelte # Homepage
|
|
250
|
+
│ ├── lib/
|
|
251
|
+
│ │ └── index.js # Empty initially
|
|
252
|
+
│ └── app.html # HTML template
|
|
253
|
+
├── static/ # Static assets (favicon, images)
|
|
254
|
+
├── svelte.config.js # SvelteKit config
|
|
255
|
+
└── package.json
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
}}}
|
|
259
|
+
|
|
260
|
+
### Step 2: Create Your First Reusable Component {{{
|
|
261
|
+
|
|
262
|
+
**Create a Navbar component:**
|
|
263
|
+
```bash
|
|
264
|
+
# Create components directory
|
|
265
|
+
mkdir -p src/lib/components
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**src/lib/components/Navbar.svelte:**
|
|
269
|
+
```svelte
|
|
270
|
+
<script>
|
|
271
|
+
let menuOpen = false;
|
|
272
|
+
|
|
273
|
+
function toggleMenu() {
|
|
274
|
+
menuOpen = !menuOpen;
|
|
275
|
+
}
|
|
276
|
+
</script>
|
|
277
|
+
|
|
278
|
+
<nav>
|
|
279
|
+
<div class="logo">MyApp</div>
|
|
280
|
+
<button on:click={toggleMenu}>Menu</button>
|
|
281
|
+
|
|
282
|
+
{#if menuOpen}
|
|
283
|
+
<ul>
|
|
284
|
+
<li><a href="/">Home</a></li>
|
|
285
|
+
<li><a href="/about">About</a></li>
|
|
286
|
+
<li><a href="/contact">Contact</a></li>
|
|
287
|
+
</ul>
|
|
288
|
+
{/if}
|
|
289
|
+
</nav>
|
|
290
|
+
|
|
291
|
+
<style>
|
|
292
|
+
nav {
|
|
293
|
+
padding: 1rem;
|
|
294
|
+
background: #333;
|
|
295
|
+
color: white;
|
|
296
|
+
}
|
|
297
|
+
</style>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Use it in your layout:**
|
|
301
|
+
```svelte
|
|
302
|
+
<!-- src/routes/+layout.svelte -->
|
|
303
|
+
<script>
|
|
304
|
+
import Navbar from '$lib/components/Navbar.svelte';
|
|
305
|
+
</script>
|
|
306
|
+
|
|
307
|
+
<Navbar />
|
|
308
|
+
<main>
|
|
309
|
+
<slot /> <!-- Pages render here -->
|
|
310
|
+
</main>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**What happened:**
|
|
314
|
+
- Created reusable Navbar in `src/lib/components/`
|
|
315
|
+
- Imported using `$lib/` alias
|
|
316
|
+
- Used in layout so it appears on all pages
|
|
317
|
+
|
|
318
|
+
}}}
|
|
319
|
+
|
|
320
|
+
### Step 3: Create a Page {{{
|
|
321
|
+
|
|
322
|
+
**Create an About page:**
|
|
323
|
+
```svelte
|
|
324
|
+
<!-- src/routes/about/+page.svelte -->
|
|
325
|
+
<script>
|
|
326
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
327
|
+
|
|
328
|
+
const today = formatDate(new Date());
|
|
329
|
+
</script>
|
|
330
|
+
|
|
331
|
+
<h1>About Us</h1>
|
|
332
|
+
<p>Page last updated: {today}</p>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Create the utility function:**
|
|
336
|
+
```javascript
|
|
337
|
+
// src/lib/utils/formatDate.js
|
|
338
|
+
export function formatDate(date) {
|
|
339
|
+
return date.toLocaleDateString('en-US', {
|
|
340
|
+
year: 'numeric',
|
|
341
|
+
month: 'long',
|
|
342
|
+
day: 'numeric'
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**URL created:** `http://localhost:5173/about`
|
|
348
|
+
|
|
349
|
+
}}}
|
|
350
|
+
|
|
351
|
+
### Step 4: Create an API Endpoint {{{
|
|
352
|
+
|
|
353
|
+
**Create a contact form endpoint:**
|
|
354
|
+
```javascript
|
|
355
|
+
// src/routes/api/contact/+server.js
|
|
356
|
+
import { json } from '@sveltejs/kit';
|
|
357
|
+
|
|
358
|
+
export async function POST({ request }) {
|
|
359
|
+
const { name, email, message } = await request.json();
|
|
360
|
+
|
|
361
|
+
// Validate
|
|
362
|
+
if (!email || !message) {
|
|
363
|
+
return json(
|
|
364
|
+
{ error: 'Email and message are required' },
|
|
365
|
+
{ status: 400 }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Process (send email, save to DB, etc.)
|
|
370
|
+
console.log('Contact form:', { name, email, message });
|
|
371
|
+
|
|
372
|
+
return json({ success: true });
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Use the endpoint from a page:**
|
|
377
|
+
```svelte
|
|
378
|
+
<!-- src/routes/contact/+page.svelte -->
|
|
379
|
+
<script>
|
|
380
|
+
let formData = { name: '', email: '', message: '' };
|
|
381
|
+
let status = '';
|
|
382
|
+
|
|
383
|
+
async function handleSubmit(e) {
|
|
384
|
+
e.preventDefault();
|
|
385
|
+
|
|
386
|
+
const response = await fetch('/api/contact', {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
headers: { 'Content-Type': 'application/json' },
|
|
389
|
+
body: JSON.stringify(formData)
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const result = await response.json();
|
|
393
|
+
status = result.success ? 'Message sent!' : 'Error sending message';
|
|
394
|
+
}
|
|
395
|
+
</script>
|
|
396
|
+
|
|
397
|
+
<form on:submit={handleSubmit}>
|
|
398
|
+
<input bind:value={formData.name} placeholder="Name" />
|
|
399
|
+
<input bind:value={formData.email} placeholder="Email" required />
|
|
400
|
+
<textarea bind:value={formData.message} placeholder="Message" required />
|
|
401
|
+
<button type="submit">Send</button>
|
|
402
|
+
</form>
|
|
403
|
+
|
|
404
|
+
{#if status}
|
|
405
|
+
<p>{status}</p>
|
|
406
|
+
{/if}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**What happened:**
|
|
410
|
+
1. Created API endpoint at `/api/contact`
|
|
411
|
+
2. Endpoint handles POST requests
|
|
412
|
+
3. Contact form page sends data to the endpoint
|
|
413
|
+
4. All API logic is in `src/routes/api/`
|
|
414
|
+
|
|
415
|
+
}}}
|
|
416
|
+
|
|
417
|
+
}}}
|
|
418
|
+
|
|
419
|
+
## 🎯 Decision Tree: Where Should This File Go? {{{
|
|
420
|
+
|
|
421
|
+
### Is it a page users can visit? {{{
|
|
422
|
+
|
|
423
|
+
**YES** → `src/routes/[name]/+page.svelte`
|
|
424
|
+
|
|
425
|
+
**Examples:**
|
|
426
|
+
- Homepage → `src/routes/+page.svelte`
|
|
427
|
+
- About page → `src/routes/about/+page.svelte`
|
|
428
|
+
- Blog post → `src/routes/blog/[slug]/+page.svelte`
|
|
429
|
+
- User profile → `src/routes/users/[id]/+page.svelte`
|
|
430
|
+
|
|
431
|
+
}}}
|
|
432
|
+
|
|
433
|
+
### Is it an API endpoint? {{{
|
|
434
|
+
|
|
435
|
+
**YES** → `src/routes/api/[name]/+server.js`
|
|
436
|
+
|
|
437
|
+
**Examples:**
|
|
438
|
+
- Contact form → `src/routes/api/contact/+server.js`
|
|
439
|
+
- User CRUD → `src/routes/api/users/+server.js`
|
|
440
|
+
- Webhook handler → `src/routes/webhook/+server.js`
|
|
441
|
+
|
|
442
|
+
**Remember:** `+server.js` files **ONLY** go in `src/routes/`!
|
|
443
|
+
|
|
444
|
+
}}}
|
|
445
|
+
|
|
446
|
+
### Is it a component used by multiple pages? {{{
|
|
447
|
+
|
|
448
|
+
**YES** → `src/lib/components/[Name].svelte`
|
|
449
|
+
|
|
450
|
+
**Examples:**
|
|
451
|
+
- Navbar → `src/lib/components/Navbar.svelte`
|
|
452
|
+
- Footer → `src/lib/components/Footer.svelte`
|
|
453
|
+
- Button → `src/lib/components/Button.svelte`
|
|
454
|
+
- Card → `src/lib/components/Card.svelte`
|
|
455
|
+
|
|
456
|
+
}}}
|
|
457
|
+
|
|
458
|
+
### Is it a utility function? {{{
|
|
459
|
+
|
|
460
|
+
**YES** → `src/lib/utils/[name].js`
|
|
461
|
+
|
|
462
|
+
**Examples:**
|
|
463
|
+
- Date formatting → `src/lib/utils/formatDate.js`
|
|
464
|
+
- Validation → `src/lib/utils/validation.js`
|
|
465
|
+
- String helpers → `src/lib/utils/strings.js`
|
|
466
|
+
|
|
467
|
+
}}}
|
|
468
|
+
|
|
469
|
+
### Does it call external APIs? {{{
|
|
470
|
+
|
|
471
|
+
**YES** → `src/lib/api/[name].js`
|
|
472
|
+
|
|
473
|
+
**Examples:**
|
|
474
|
+
- Email service → `src/lib/api/emailClient.js`
|
|
475
|
+
- Payment processing → `src/lib/api/stripe.js`
|
|
476
|
+
- External API wrapper → `src/lib/api/github.js`
|
|
477
|
+
|
|
478
|
+
}}}
|
|
479
|
+
|
|
480
|
+
### Is it database-related? {{{
|
|
481
|
+
|
|
482
|
+
**YES** → `src/lib/db/[name].js`
|
|
483
|
+
|
|
484
|
+
**Examples:**
|
|
485
|
+
- Database queries → `src/lib/db/queries.js`
|
|
486
|
+
- Database connection → `src/lib/db/connection.js`
|
|
487
|
+
- ORM models → `src/lib/db/models.js`
|
|
488
|
+
|
|
489
|
+
}}}
|
|
490
|
+
|
|
491
|
+
### Is it server-only page logic? {{{
|
|
492
|
+
|
|
493
|
+
**YES** → `src/routes/[path]/+page.server.js`
|
|
494
|
+
|
|
495
|
+
**Use case:** Load data on the server before rendering the page
|
|
496
|
+
|
|
497
|
+
**Example:**
|
|
498
|
+
```javascript
|
|
499
|
+
// src/routes/blog/[slug]/+page.server.js
|
|
500
|
+
export async function load({ params }) {
|
|
501
|
+
const post = await db.getPost(params.slug);
|
|
502
|
+
return { post };
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
}}}
|
|
507
|
+
|
|
508
|
+
}}}
|
|
509
|
+
|
|
510
|
+
## 🧪 Practice Exercises {{{
|
|
511
|
+
|
|
512
|
+
### Exercise 1: Build a Reusable Card Component {{{
|
|
513
|
+
|
|
514
|
+
**Goal:** Create a card component in `src/lib` and use it on multiple pages
|
|
515
|
+
|
|
516
|
+
**Create the component:**
|
|
517
|
+
```svelte
|
|
518
|
+
<!-- src/lib/components/Card.svelte -->
|
|
519
|
+
<script>
|
|
520
|
+
export let title;
|
|
521
|
+
export let description;
|
|
522
|
+
export let href = null;
|
|
523
|
+
</script>
|
|
524
|
+
|
|
525
|
+
<div class="card">
|
|
526
|
+
<h3>{title}</h3>
|
|
527
|
+
<p>{description}</p>
|
|
528
|
+
{#if href}
|
|
529
|
+
<a {href}>Learn more →</a>
|
|
530
|
+
{/if}
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<style>
|
|
534
|
+
.card {
|
|
535
|
+
border: 1px solid #ddd;
|
|
536
|
+
border-radius: 8px;
|
|
537
|
+
padding: 1.5rem;
|
|
538
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
h3 {
|
|
542
|
+
margin-top: 0;
|
|
543
|
+
}
|
|
544
|
+
</style>
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**Use it on homepage:**
|
|
548
|
+
```svelte
|
|
549
|
+
<!-- src/routes/+page.svelte -->
|
|
550
|
+
<script>
|
|
551
|
+
import Card from '$lib/components/Card.svelte';
|
|
552
|
+
</script>
|
|
553
|
+
|
|
554
|
+
<h1>Welcome</h1>
|
|
555
|
+
|
|
556
|
+
<div class="grid">
|
|
557
|
+
<Card
|
|
558
|
+
title="Feature 1"
|
|
559
|
+
description="Amazing feature description"
|
|
560
|
+
href="/features/1"
|
|
561
|
+
/>
|
|
562
|
+
<Card
|
|
563
|
+
title="Feature 2"
|
|
564
|
+
description="Another cool feature"
|
|
565
|
+
href="/features/2"
|
|
566
|
+
/>
|
|
567
|
+
</div>
|
|
568
|
+
|
|
569
|
+
<style>
|
|
570
|
+
.grid {
|
|
571
|
+
display: grid;
|
|
572
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
573
|
+
gap: 1rem;
|
|
574
|
+
}
|
|
575
|
+
</style>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**Key learning:** Components in `src/lib/components/` can be reused anywhere with the `$lib` alias.
|
|
579
|
+
|
|
580
|
+
}}}
|
|
581
|
+
|
|
582
|
+
### Exercise 2: Create a Todo API {{{
|
|
583
|
+
|
|
584
|
+
**Goal:** Build a simple todo API with GET and POST endpoints
|
|
585
|
+
|
|
586
|
+
**Create the API endpoint:**
|
|
587
|
+
```javascript
|
|
588
|
+
// src/routes/api/todos/+server.js
|
|
589
|
+
import { json } from '@sveltejs/kit';
|
|
590
|
+
|
|
591
|
+
// In-memory storage (would be a database in production)
|
|
592
|
+
let todos = [
|
|
593
|
+
{ id: 1, text: 'Learn SvelteKit', done: false },
|
|
594
|
+
{ id: 2, text: 'Build an app', done: false }
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
let nextId = 3;
|
|
598
|
+
|
|
599
|
+
// GET all todos
|
|
600
|
+
export async function GET() {
|
|
601
|
+
return json(todos);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// POST new todo
|
|
605
|
+
export async function POST({ request }) {
|
|
606
|
+
const { text } = await request.json();
|
|
607
|
+
|
|
608
|
+
if (!text) {
|
|
609
|
+
return json({ error: 'Text is required' }, { status: 400 });
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const newTodo = {
|
|
613
|
+
id: nextId++,
|
|
614
|
+
text,
|
|
615
|
+
done: false
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
todos.push(newTodo);
|
|
619
|
+
return json(newTodo, { status: 201 });
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// DELETE todo
|
|
623
|
+
export async function DELETE({ request }) {
|
|
624
|
+
const { id } = await request.json();
|
|
625
|
+
todos = todos.filter(todo => todo.id !== id);
|
|
626
|
+
return json({ success: true });
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
**Create a page to use the API:**
|
|
631
|
+
```svelte
|
|
632
|
+
<!-- src/routes/todos/+page.svelte -->
|
|
633
|
+
<script>
|
|
634
|
+
import { onMount } from 'svelte';
|
|
635
|
+
|
|
636
|
+
let todos = [];
|
|
637
|
+
let newTodoText = '';
|
|
638
|
+
|
|
639
|
+
onMount(async () => {
|
|
640
|
+
await loadTodos();
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
async function loadTodos() {
|
|
644
|
+
const response = await fetch('/api/todos');
|
|
645
|
+
todos = await response.json();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function addTodo() {
|
|
649
|
+
if (!newTodoText.trim()) return;
|
|
650
|
+
|
|
651
|
+
await fetch('/api/todos', {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
headers: { 'Content-Type': 'application/json' },
|
|
654
|
+
body: JSON.stringify({ text: newTodoText })
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
newTodoText = '';
|
|
658
|
+
await loadTodos();
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function deleteTodo(id) {
|
|
662
|
+
await fetch('/api/todos', {
|
|
663
|
+
method: 'DELETE',
|
|
664
|
+
headers: { 'Content-Type': 'application/json' },
|
|
665
|
+
body: JSON.stringify({ id })
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
await loadTodos();
|
|
669
|
+
}
|
|
670
|
+
</script>
|
|
671
|
+
|
|
672
|
+
<h1>My Todos</h1>
|
|
673
|
+
|
|
674
|
+
<form on:submit|preventDefault={addTodo}>
|
|
675
|
+
<input bind:value={newTodoText} placeholder="New todo..." />
|
|
676
|
+
<button type="submit">Add</button>
|
|
677
|
+
</form>
|
|
678
|
+
|
|
679
|
+
<ul>
|
|
680
|
+
{#each todos as todo}
|
|
681
|
+
<li>
|
|
682
|
+
{todo.text}
|
|
683
|
+
<button on:click={() => deleteTodo(todo.id)}>Delete</button>
|
|
684
|
+
</li>
|
|
685
|
+
{/each}
|
|
686
|
+
</ul>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
**What you built:**
|
|
690
|
+
- API endpoint at `/api/todos` with GET, POST, DELETE
|
|
691
|
+
- Todo list page at `/todos` that uses the API
|
|
692
|
+
- Full CRUD operations for todos
|
|
693
|
+
|
|
694
|
+
}}}
|
|
695
|
+
|
|
696
|
+
### Exercise 3: Organize a Full App Structure {{{
|
|
697
|
+
|
|
698
|
+
**Goal:** Build a blog with proper file organization
|
|
699
|
+
|
|
700
|
+
**Structure:**
|
|
701
|
+
```
|
|
702
|
+
src/
|
|
703
|
+
├── lib/
|
|
704
|
+
│ ├── components/
|
|
705
|
+
│ │ ├── Navbar.svelte
|
|
706
|
+
│ │ ├── Footer.svelte
|
|
707
|
+
│ │ └── BlogCard.svelte
|
|
708
|
+
│ ├── utils/
|
|
709
|
+
│ │ ├── formatDate.js
|
|
710
|
+
│ │ └── slugify.js
|
|
711
|
+
│ └── db/
|
|
712
|
+
│ └── posts.js # Mock database
|
|
713
|
+
└── routes/
|
|
714
|
+
├── +layout.svelte # App layout with Navbar/Footer
|
|
715
|
+
├── +page.svelte # Homepage
|
|
716
|
+
├── blog/
|
|
717
|
+
│ ├── +page.svelte # Blog list
|
|
718
|
+
│ ├── +page.server.js # Load posts
|
|
719
|
+
│ └── [slug]/
|
|
720
|
+
│ ├── +page.svelte # Single post
|
|
721
|
+
│ └── +page.server.js
|
|
722
|
+
└── api/
|
|
723
|
+
└── posts/
|
|
724
|
+
└── +server.js # Posts API
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Mock database:**
|
|
728
|
+
```javascript
|
|
729
|
+
// src/lib/db/posts.js
|
|
730
|
+
export const posts = [
|
|
731
|
+
{
|
|
732
|
+
slug: 'svelte-basics',
|
|
733
|
+
title: 'SvelteKit Basics',
|
|
734
|
+
excerpt: 'Learn the fundamentals...',
|
|
735
|
+
content: 'Full post content here...',
|
|
736
|
+
date: new Date('2025-01-01')
|
|
737
|
+
},
|
|
738
|
+
{
|
|
739
|
+
slug: 'routing-guide',
|
|
740
|
+
title: 'Routing in SvelteKit',
|
|
741
|
+
excerpt: 'Understanding file-based routing...',
|
|
742
|
+
content: 'Full routing guide...',
|
|
743
|
+
date: new Date('2025-01-15')
|
|
744
|
+
}
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
export function getAllPosts() {
|
|
748
|
+
return posts;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
export function getPostBySlug(slug) {
|
|
752
|
+
return posts.find(post => post.slug === slug);
|
|
753
|
+
}
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Blog list page:**
|
|
757
|
+
```javascript
|
|
758
|
+
// src/routes/blog/+page.server.js
|
|
759
|
+
import { getAllPosts } from '$lib/db/posts.js';
|
|
760
|
+
|
|
761
|
+
export function load() {
|
|
762
|
+
const posts = getAllPosts();
|
|
763
|
+
return { posts };
|
|
764
|
+
}
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
```svelte
|
|
768
|
+
<!-- src/routes/blog/+page.svelte -->
|
|
769
|
+
<script>
|
|
770
|
+
export let data;
|
|
771
|
+
import BlogCard from '$lib/components/BlogCard.svelte';
|
|
772
|
+
</script>
|
|
773
|
+
|
|
774
|
+
<h1>Blog</h1>
|
|
775
|
+
|
|
776
|
+
<div class="posts">
|
|
777
|
+
{#each data.posts as post}
|
|
778
|
+
<BlogCard {post} />
|
|
779
|
+
{/each}
|
|
780
|
+
</div>
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
**Single post page:**
|
|
784
|
+
```javascript
|
|
785
|
+
// src/routes/blog/[slug]/+page.server.js
|
|
786
|
+
import { getPostBySlug } from '$lib/db/posts.js';
|
|
787
|
+
import { error } from '@sveltejs/kit';
|
|
788
|
+
|
|
789
|
+
export function load({ params }) {
|
|
790
|
+
const post = getPostBySlug(params.slug);
|
|
791
|
+
|
|
792
|
+
if (!post) {
|
|
793
|
+
throw error(404, 'Post not found');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return { post };
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
```svelte
|
|
801
|
+
<!-- src/routes/blog/[slug]/+page.svelte -->
|
|
802
|
+
<script>
|
|
803
|
+
export let data;
|
|
804
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
805
|
+
</script>
|
|
806
|
+
|
|
807
|
+
<article>
|
|
808
|
+
<h1>{data.post.title}</h1>
|
|
809
|
+
<time>{formatDate(data.post.date)}</time>
|
|
810
|
+
<div class="content">
|
|
811
|
+
{@html data.post.content}
|
|
812
|
+
</div>
|
|
813
|
+
</article>
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**Key learning:** Proper separation of concerns - components in `lib/`, pages in `routes/`, data logic in `lib/db/`.
|
|
817
|
+
|
|
818
|
+
}}}
|
|
819
|
+
|
|
820
|
+
}}}
|
|
821
|
+
|
|
822
|
+
## 🛡️ Best Practices {{{
|
|
823
|
+
|
|
824
|
+
### Keep Routes Clean {{{
|
|
825
|
+
|
|
826
|
+
**❌ Bad - Logic in page component:**
|
|
827
|
+
```svelte
|
|
828
|
+
<!-- src/routes/users/+page.svelte -->
|
|
829
|
+
<script>
|
|
830
|
+
import { onMount } from 'svelte';
|
|
831
|
+
|
|
832
|
+
let users = [];
|
|
833
|
+
|
|
834
|
+
onMount(async () => {
|
|
835
|
+
const response = await fetch('https://api.example.com/users');
|
|
836
|
+
users = await response.json();
|
|
837
|
+
});
|
|
838
|
+
</script>
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
**✅ Good - Logic in server file:**
|
|
842
|
+
```javascript
|
|
843
|
+
// src/routes/users/+page.server.js
|
|
844
|
+
export async function load({ fetch }) {
|
|
845
|
+
const response = await fetch('https://api.example.com/users');
|
|
846
|
+
const users = await response.json();
|
|
847
|
+
return { users };
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
```svelte
|
|
852
|
+
<!-- src/routes/users/+page.svelte -->
|
|
853
|
+
<script>
|
|
854
|
+
export let data;
|
|
855
|
+
</script>
|
|
856
|
+
|
|
857
|
+
<h1>Users</h1>
|
|
858
|
+
{#each data.users as user}
|
|
859
|
+
<p>{user.name}</p>
|
|
860
|
+
{/each}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**Why:** Server-side loading is faster and more secure (API keys stay on server).
|
|
864
|
+
|
|
865
|
+
}}}
|
|
866
|
+
|
|
867
|
+
### Extract Reusable Logic to $lib {{{
|
|
868
|
+
|
|
869
|
+
**❌ Bad - Duplicated validation:**
|
|
870
|
+
```svelte
|
|
871
|
+
<!-- src/routes/contact/+page.svelte -->
|
|
872
|
+
<script>
|
|
873
|
+
function validateEmail(email) {
|
|
874
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
875
|
+
}
|
|
876
|
+
</script>
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
```svelte
|
|
880
|
+
<!-- src/routes/signup/+page.svelte -->
|
|
881
|
+
<script>
|
|
882
|
+
function validateEmail(email) {
|
|
883
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
884
|
+
}
|
|
885
|
+
</script>
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
**✅ Good - Shared utility:**
|
|
889
|
+
```javascript
|
|
890
|
+
// src/lib/utils/validation.js
|
|
891
|
+
export function validateEmail(email) {
|
|
892
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
893
|
+
}
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
```svelte
|
|
897
|
+
<!-- Both pages -->
|
|
898
|
+
<script>
|
|
899
|
+
import { validateEmail } from '$lib/utils/validation.js';
|
|
900
|
+
</script>
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
}}}
|
|
904
|
+
|
|
905
|
+
### Use Proper Import Paths {{{
|
|
906
|
+
|
|
907
|
+
**❌ Bad - Relative imports:**
|
|
908
|
+
```javascript
|
|
909
|
+
import Footer from '../../lib/components/Footer.svelte';
|
|
910
|
+
import { formatDate } from '../../../lib/utils/formatDate.js';
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
**✅ Good - $lib alias:**
|
|
914
|
+
```javascript
|
|
915
|
+
import Footer from '$lib/components/Footer.svelte';
|
|
916
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
**Why:** Easier to refactor, clearer intent, no need to count `../`.
|
|
920
|
+
|
|
921
|
+
}}}
|
|
922
|
+
|
|
923
|
+
### Separate API Client from API Routes {{{
|
|
924
|
+
|
|
925
|
+
**Confusion to avoid:**
|
|
926
|
+
```
|
|
927
|
+
src/routes/api/email/+server.js ← API endpoint you expose
|
|
928
|
+
src/lib/api/email.js ← Helper that calls external email service
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
**Example:**
|
|
932
|
+
```javascript
|
|
933
|
+
// src/lib/api/emailClient.js
|
|
934
|
+
// Helper function that calls SendGrid (external API)
|
|
935
|
+
export async function sendEmail(to, subject, body) {
|
|
936
|
+
const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
|
|
937
|
+
method: 'POST',
|
|
938
|
+
headers: {
|
|
939
|
+
'Authorization': `Bearer ${import.meta.env.VITE_SENDGRID_KEY}`,
|
|
940
|
+
'Content-Type': 'application/json'
|
|
941
|
+
},
|
|
942
|
+
body: JSON.stringify({ to, subject, body })
|
|
943
|
+
});
|
|
944
|
+
return response.json();
|
|
945
|
+
}
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
```javascript
|
|
949
|
+
// src/routes/api/contact/+server.js
|
|
950
|
+
// Your API endpoint that uses the helper
|
|
951
|
+
import { json } from '@sveltejs/kit';
|
|
952
|
+
import { sendEmail } from '$lib/api/emailClient.js';
|
|
953
|
+
|
|
954
|
+
export async function POST({ request }) {
|
|
955
|
+
const { email, message } = await request.json();
|
|
956
|
+
await sendEmail(email, 'Contact Form', message);
|
|
957
|
+
return json({ success: true });
|
|
958
|
+
}
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
**Remember:**
|
|
962
|
+
- `src/lib/api/` = Functions that CALL external APIs (tools)
|
|
963
|
+
- `src/routes/api/` = Endpoints your app EXPOSES (routes)
|
|
964
|
+
|
|
965
|
+
}}}
|
|
966
|
+
|
|
967
|
+
### Don't Overengineer with Barrel Exports {{{
|
|
968
|
+
|
|
969
|
+
**❌ Overkill for small projects:**
|
|
970
|
+
```javascript
|
|
971
|
+
// src/lib/index.js
|
|
972
|
+
export { default as Navbar } from './components/Navbar.svelte';
|
|
973
|
+
export { default as Footer } from './components/Footer.svelte';
|
|
974
|
+
export { default as Button } from './components/Button.svelte';
|
|
975
|
+
export { formatDate } from './utils/formatDate.js';
|
|
976
|
+
export { validateEmail } from './utils/validation.js';
|
|
977
|
+
// ... 50 more exports
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
**✅ Keep it simple:**
|
|
981
|
+
```javascript
|
|
982
|
+
// Just import directly
|
|
983
|
+
import Navbar from '$lib/components/Navbar.svelte';
|
|
984
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
**When barrel exports make sense:**
|
|
988
|
+
- You have a component library with 20+ components
|
|
989
|
+
- You want consistent import patterns across a large team
|
|
990
|
+
- You're building a published package
|
|
991
|
+
|
|
992
|
+
**For most projects:** Direct imports are clearer and easier to maintain.
|
|
993
|
+
|
|
994
|
+
}}}
|
|
995
|
+
|
|
996
|
+
}}}
|
|
997
|
+
|
|
998
|
+
## 📚 Quick Reference {{{
|
|
999
|
+
|
|
1000
|
+
### File Naming Conventions {{{
|
|
1001
|
+
|
|
1002
|
+
```
|
|
1003
|
+
+page.svelte → Page component (URL endpoint)
|
|
1004
|
+
+page.server.js → Server-side page logic
|
|
1005
|
+
+layout.svelte → Layout wrapping multiple pages
|
|
1006
|
+
+layout.server.js → Server-side layout logic
|
|
1007
|
+
+server.js → API endpoint (GET, POST, etc.)
|
|
1008
|
+
+error.svelte → Error page
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
}}}
|
|
1012
|
+
|
|
1013
|
+
### Directory Mapping {{{
|
|
1014
|
+
|
|
1015
|
+
```
|
|
1016
|
+
src/routes/ → Pages and API endpoints (public URLs)
|
|
1017
|
+
src/lib/ → Reusable code (internal)
|
|
1018
|
+
├── components/ → Reusable UI components
|
|
1019
|
+
├── utils/ → Helper functions
|
|
1020
|
+
├── api/ → External API client functions
|
|
1021
|
+
├── db/ → Database queries and models
|
|
1022
|
+
└── index.js → Optional barrel exports
|
|
1023
|
+
|
|
1024
|
+
static/ → Static assets (images, fonts)
|
|
1025
|
+
└── favicon.png → Available at /favicon.png
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
}}}
|
|
1029
|
+
|
|
1030
|
+
### Import Patterns {{{
|
|
1031
|
+
|
|
1032
|
+
```javascript
|
|
1033
|
+
// Components
|
|
1034
|
+
import Navbar from '$lib/components/Navbar.svelte';
|
|
1035
|
+
import Footer from '$lib/components/Footer.svelte';
|
|
1036
|
+
|
|
1037
|
+
// Utilities
|
|
1038
|
+
import { formatDate } from '$lib/utils/formatDate.js';
|
|
1039
|
+
import { validateEmail } from '$lib/utils/validation.js';
|
|
1040
|
+
|
|
1041
|
+
// API clients
|
|
1042
|
+
import { sendEmail } from '$lib/api/emailClient.js';
|
|
1043
|
+
|
|
1044
|
+
// Database
|
|
1045
|
+
import { getUsers } from '$lib/db/queries.js';
|
|
1046
|
+
|
|
1047
|
+
// From static folder
|
|
1048
|
+
<img src="/logo.png" alt="Logo">
|
|
1049
|
+
```
|
|
1050
|
+
|
|
1051
|
+
}}}
|
|
1052
|
+
|
|
1053
|
+
### Common Patterns {{{
|
|
1054
|
+
|
|
1055
|
+
**Load data server-side:**
|
|
1056
|
+
```javascript
|
|
1057
|
+
// +page.server.js
|
|
1058
|
+
export async function load() {
|
|
1059
|
+
const data = await fetchData();
|
|
1060
|
+
return { data };
|
|
1061
|
+
}
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
**Create API endpoint:**
|
|
1065
|
+
```javascript
|
|
1066
|
+
// +server.js
|
|
1067
|
+
import { json } from '@sveltejs/kit';
|
|
1068
|
+
|
|
1069
|
+
export async function POST({ request }) {
|
|
1070
|
+
const data = await request.json();
|
|
1071
|
+
return json({ success: true });
|
|
1072
|
+
}
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
**Dynamic routes:**
|
|
1076
|
+
```
|
|
1077
|
+
src/routes/blog/[slug]/+page.svelte → /blog/anything
|
|
1078
|
+
src/routes/users/[id]/+page.svelte → /users/123
|
|
1079
|
+
src/routes/[...catchall]/+page.svelte → Matches everything
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
}}}
|
|
1083
|
+
|
|
1084
|
+
}}}
|
|
1085
|
+
|
|
1086
|
+
## 🐛 Troubleshooting {{{
|
|
1087
|
+
|
|
1088
|
+
### "$lib not found" Error {{{
|
|
1089
|
+
|
|
1090
|
+
**Symptom:**
|
|
1091
|
+
```
|
|
1092
|
+
Error: Cannot find module '$lib/components/Navbar.svelte'
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
**Common causes:**
|
|
1096
|
+
1. File doesn't exist at that path
|
|
1097
|
+
2. Wrong file extension (`.svelte` vs `.js`)
|
|
1098
|
+
3. Typo in path
|
|
1099
|
+
|
|
1100
|
+
**Fix:**
|
|
1101
|
+
```bash
|
|
1102
|
+
# Verify file exists
|
|
1103
|
+
ls src/lib/components/Navbar.svelte
|
|
1104
|
+
|
|
1105
|
+
# Check import statement matches actual file name (case-sensitive)
|
|
1106
|
+
import Navbar from '$lib/components/Navbar.svelte'; # Correct
|
|
1107
|
+
import Navbar from '$lib/components/navbar.svelte'; # Wrong if file is Navbar.svelte
|
|
1108
|
+
```
|
|
1109
|
+
|
|
1110
|
+
}}}
|
|
1111
|
+
|
|
1112
|
+
### "Cannot read property of undefined" in Component {{{
|
|
1113
|
+
|
|
1114
|
+
**Symptom:**
|
|
1115
|
+
```javascript
|
|
1116
|
+
<!-- BlogCard.svelte -->
|
|
1117
|
+
<h2>{post.title}</h2> <!-- Error: post is undefined -->
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
**Common cause:** Forgot to export the prop
|
|
1121
|
+
|
|
1122
|
+
**Fix:**
|
|
1123
|
+
```svelte
|
|
1124
|
+
<script>
|
|
1125
|
+
export let post; // Must export props!
|
|
1126
|
+
</script>
|
|
1127
|
+
|
|
1128
|
+
<h2>{post.title}</h2>
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
}}}
|
|
1132
|
+
|
|
1133
|
+
### API Endpoint Not Working {{{
|
|
1134
|
+
|
|
1135
|
+
**Symptom:**
|
|
1136
|
+
```
|
|
1137
|
+
404 Not Found when calling /api/contact
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
**Checklist:**
|
|
1141
|
+
```bash
|
|
1142
|
+
# 1. Verify file exists at correct location
|
|
1143
|
+
ls src/routes/api/contact/+server.js
|
|
1144
|
+
|
|
1145
|
+
# 2. Check file exports HTTP methods
|
|
1146
|
+
# File must export: GET, POST, PUT, DELETE, etc.
|
|
1147
|
+
|
|
1148
|
+
# 3. Restart dev server
|
|
1149
|
+
# Sometimes needed after creating new +server.js files
|
|
1150
|
+
Ctrl+C
|
|
1151
|
+
npm run dev
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
**Common mistake:**
|
|
1155
|
+
```javascript
|
|
1156
|
+
// ❌ Wrong - not exporting
|
|
1157
|
+
async function POST() { ... }
|
|
1158
|
+
|
|
1159
|
+
// ✅ Correct
|
|
1160
|
+
export async function POST() { ... }
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
}}}
|
|
1164
|
+
|
|
1165
|
+
### Layout Not Applying to Page {{{
|
|
1166
|
+
|
|
1167
|
+
**Symptom:** Navbar/Footer not showing on a page
|
|
1168
|
+
|
|
1169
|
+
**Cause:** Layout must be in parent directory
|
|
1170
|
+
|
|
1171
|
+
**Structure:**
|
|
1172
|
+
```
|
|
1173
|
+
src/routes/
|
|
1174
|
+
├── +layout.svelte ← Applies to ALL pages
|
|
1175
|
+
├── +page.svelte ← Homepage (has layout)
|
|
1176
|
+
└── blog/
|
|
1177
|
+
├── +layout.svelte ← Applies only to /blog routes
|
|
1178
|
+
└── +page.svelte ← Blog page (has both layouts)
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
**Layout must have `<slot />`:**
|
|
1182
|
+
```svelte
|
|
1183
|
+
<!-- +layout.svelte -->
|
|
1184
|
+
<Navbar />
|
|
1185
|
+
<slot /> <!-- Pages render here -->
|
|
1186
|
+
<Footer />
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
}}}
|
|
1190
|
+
|
|
1191
|
+
### Component Not Re-rendering {{{
|
|
1192
|
+
|
|
1193
|
+
**Symptom:** Component doesn't update when data changes
|
|
1194
|
+
|
|
1195
|
+
**Common cause:** Not using reactive statements
|
|
1196
|
+
|
|
1197
|
+
**Fix with Svelte 5 runes:**
|
|
1198
|
+
```svelte
|
|
1199
|
+
<script>
|
|
1200
|
+
// Old way (Svelte 4)
|
|
1201
|
+
let count = 0;
|
|
1202
|
+
$: doubled = count * 2; // Reactive statement
|
|
1203
|
+
|
|
1204
|
+
// New way (Svelte 5)
|
|
1205
|
+
let count = $state(0);
|
|
1206
|
+
let doubled = $derived(count * 2); // Derived state
|
|
1207
|
+
</script>
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
}}}
|
|
1211
|
+
|
|
1212
|
+
}}}
|
|
1213
|
+
|
|
1214
|
+
## 💡 Pro Tips {{{
|
|
1215
|
+
|
|
1216
|
+
1. **Use +page.server.js for data fetching:**
|
|
1217
|
+
- Keeps API keys secure
|
|
1218
|
+
- Faster initial load
|
|
1219
|
+
- Better SEO
|
|
1220
|
+
|
|
1221
|
+
2. **Colocate related route files:**
|
|
1222
|
+
```
|
|
1223
|
+
src/routes/blog/[slug]/
|
|
1224
|
+
├── +page.svelte
|
|
1225
|
+
├── +page.server.js
|
|
1226
|
+
└── BlogComments.svelte ← Component only used by this page
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
3. **Use +layout.svelte for shared UI:**
|
|
1230
|
+
```svelte
|
|
1231
|
+
<!-- src/routes/+layout.svelte -->
|
|
1232
|
+
<script>
|
|
1233
|
+
import Navbar from '$lib/components/Navbar.svelte';
|
|
1234
|
+
import Footer from '$lib/components/Footer.svelte';
|
|
1235
|
+
</script>
|
|
1236
|
+
|
|
1237
|
+
<Navbar />
|
|
1238
|
+
<main>
|
|
1239
|
+
<slot /> <!-- All pages render here -->
|
|
1240
|
+
</main>
|
|
1241
|
+
<Footer />
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
4. **Organize by feature, not file type:**
|
|
1245
|
+
```
|
|
1246
|
+
✅ Good:
|
|
1247
|
+
src/lib/
|
|
1248
|
+
├── auth/
|
|
1249
|
+
│ ├── Login.svelte
|
|
1250
|
+
│ ├── Signup.svelte
|
|
1251
|
+
│ └── auth.js
|
|
1252
|
+
└── blog/
|
|
1253
|
+
├── BlogCard.svelte
|
|
1254
|
+
└── blogUtils.js
|
|
1255
|
+
|
|
1256
|
+
❌ Less maintainable:
|
|
1257
|
+
src/lib/
|
|
1258
|
+
├── components/
|
|
1259
|
+
│ ├── Login.svelte
|
|
1260
|
+
│ ├── Signup.svelte
|
|
1261
|
+
│ └── BlogCard.svelte
|
|
1262
|
+
└── utils/
|
|
1263
|
+
├── auth.js
|
|
1264
|
+
└── blogUtils.js
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
5. **Use TypeScript JSDoc for better autocomplete (without TypeScript):**
|
|
1268
|
+
```javascript
|
|
1269
|
+
// src/lib/utils/formatDate.js
|
|
1270
|
+
/**
|
|
1271
|
+
* Format a date for display
|
|
1272
|
+
* @param {Date} date - The date to format
|
|
1273
|
+
* @returns {string} Formatted date string
|
|
1274
|
+
*/
|
|
1275
|
+
export function formatDate(date) {
|
|
1276
|
+
return date.toLocaleDateString();
|
|
1277
|
+
}
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
6. **Create a $lib/config.js for constants:**
|
|
1281
|
+
```javascript
|
|
1282
|
+
// src/lib/config.js
|
|
1283
|
+
export const SITE_NAME = 'My Awesome App';
|
|
1284
|
+
export const MAX_UPLOAD_SIZE = 5 * 1024 * 1024; // 5MB
|
|
1285
|
+
export const API_URL = import.meta.env.VITE_API_URL;
|
|
1286
|
+
```
|
|
1287
|
+
|
|
1288
|
+
7. **Use the static/ folder for public assets:**
|
|
1289
|
+
```
|
|
1290
|
+
static/
|
|
1291
|
+
├── favicon.png → /favicon.png
|
|
1292
|
+
├── logo.svg → /logo.svg
|
|
1293
|
+
└── images/
|
|
1294
|
+
└── hero.jpg → /images/hero.jpg
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
```svelte
|
|
1298
|
+
<img src="/images/hero.jpg" alt="Hero">
|
|
1299
|
+
```
|
|
1300
|
+
|
|
1301
|
+
8. **Create error pages:**
|
|
1302
|
+
```svelte
|
|
1303
|
+
<!-- src/routes/+error.svelte -->
|
|
1304
|
+
<script>
|
|
1305
|
+
import { page } from '$app/stores';
|
|
1306
|
+
</script>
|
|
1307
|
+
|
|
1308
|
+
<h1>{$page.status}: {$page.error.message}</h1>
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
}}}
|
|
1312
|
+
|
|
1313
|
+
## 🚀 Next Steps {{{
|
|
1314
|
+
|
|
1315
|
+
**Master these progressively:**
|
|
1316
|
+
|
|
1317
|
+
1. ✅ **Level 1:** Understand `src/routes` vs `src/lib`
|
|
1318
|
+
2. ✅ **Level 2:** Create reusable components with `$lib`
|
|
1319
|
+
3. ✅ **Level 3:** Build API endpoints with `+server.js`
|
|
1320
|
+
4. 🎯 **Level 4:** Server-side data loading with `+page.server.js`
|
|
1321
|
+
5. 🎯 **Level 5:** Advanced routing (layouts, groups, optional params)
|
|
1322
|
+
6. 🎯 **Level 6:** Form actions and progressive enhancement
|
|
1323
|
+
|
|
1324
|
+
**Related concepts:**
|
|
1325
|
+
- **SvelteKit routing** - Nested layouts, dynamic routes, route groups
|
|
1326
|
+
- **Form actions** - Handle form submissions without JavaScript
|
|
1327
|
+
- **Stores** - Global state management with `$app/stores`
|
|
1328
|
+
- **Hooks** - Intercept requests with `hooks.server.js`
|
|
1329
|
+
- **Adapters** - Deploy to Vercel, Netlify, Node.js, etc.
|
|
1330
|
+
|
|
1331
|
+
**Resources:**
|
|
1332
|
+
- SvelteKit Docs: https://svelte.dev/docs/kit
|
|
1333
|
+
- Svelte Tutorial: https://svelte.dev/tutorial
|
|
1334
|
+
- SvelteKit LLM Docs: https://svelte.dev/docs/kit/llms-small.txt
|
|
1335
|
+
|
|
1336
|
+
}}}
|
|
1337
|
+
|
|
1338
|
+
---
|
|
1339
|
+
|
|
1340
|
+
**Remember:** File structure in SvelteKit is intuitive once you understand the core principle: `src/routes/` is for URLs (pages and APIs), `src/lib/` is for your code toolbox. When in doubt, ask: "Do users visit this directly?" If yes → routes. If no → lib. 🚀
|