SvelteKit ships with a built-in API layer that's both elegant and powerful. If you're building full-stack apps, SvelteKit's server-side features eliminate the need for a separate backend.
Load Functions — The Heart of SvelteKit
Every route can have a +page.server.js that loads data before rendering:
// src/routes/posts/+page.server.js
export async function load({ url, fetch }) {
const page = url.searchParams.get("page") || 1
const posts = await db.post.findMany({
skip: (page - 1) * 10,
take: 10,
orderBy: { createdAt: "desc" }
})
const total = await db.post.count()
return { posts, total, page: Number(page) }
}
<!-- src/routes/posts/+page.svelte -->
<script>
export let data
</script>
{#each data.posts as post}
<article>
<h2><a href="/posts/{post.slug}">{post.title}</a></h2>
<p>{post.excerpt}</p>
</article>
{/each}
Form Actions — Mutations Made Simple
SvelteKit handles form submissions with progressive enhancement:
// src/routes/posts/new/+page.server.js
import { fail, redirect } from "@sveltejs/kit"
export const actions = {
default: async ({ request }) => {
const formData = await request.formData()
const title = formData.get("title")
const content = formData.get("content")
if (!title || title.length < 3) {
return fail(400, { title, content, error: "Title must be 3+ chars" })
}
const post = await db.post.create({ data: { title, content } })
throw redirect(303, `/posts/${post.slug}`)
}
}
<!-- src/routes/posts/new/+page.svelte -->
<script>
import { enhance } from "$app/forms"
export let form
</script>
<form method="POST" use:enhance>
<input name="title" value={form?.title ?? ""} />
{#if form?.error}<p class="error">{form.error}</p>{/if}
<textarea name="content">{form?.content ?? ""}</textarea>
<button>Publish</button>
</form>
API Routes (Server Endpoints)
Create pure API endpoints with +server.js:
// src/routes/api/posts/+server.js
import { json } from "@sveltejs/kit"
export async function GET({ url }) {
const limit = Number(url.searchParams.get("limit") || 10)
const posts = await db.post.findMany({ take: limit })
return json(posts)
}
export async function POST({ request }) {
const body = await request.json()
const post = await db.post.create({ data: body })
return json(post, { status: 201 })
}
export async function DELETE({ url }) {
const id = url.searchParams.get("id")
await db.post.delete({ where: { id } })
return new Response(null, { status: 204 })
}
Hooks — Global Request Processing
SvelteKit hooks intercept every request:
// src/hooks.server.js
export async function handle({ event, resolve }) {
// Auth check
const session = event.cookies.get("session")
if (session) {
event.locals.user = await getUserFromSession(session)
}
// Rate limiting
const ip = event.getClientAddress()
if (isRateLimited(ip)) {
return new Response("Too many requests", { status: 429 })
}
const response = await resolve(event)
response.headers.set("X-Frame-Options", "DENY")
return response
}
Streaming with Promises
Return promises to stream data:
// src/routes/dashboard/+page.server.js
export async function load() {
return {
quickStats: await getQuickStats(),
// This streams — page renders immediately with quickStats
heavyReport: getHeavyReport() // No await = streams!
}
}
<script>
export let data
</script>
<h1>Dashboard</h1>
<QuickStats stats={data.quickStats} />
{#await data.heavyReport}
<p>Loading report...</p>
{:then report}
<HeavyReport {report} />
{/await}
Key Takeaways
- Load functions fetch data server-side with automatic typing
- Form actions handle mutations with progressive enhancement
- +server.js files create pure API endpoints
- Hooks intercept all requests for auth, logging, rate limiting
- Streaming with promises for instant page loads
- Zero-JS forms work without client-side JavaScript
Dive into SvelteKit docs for the full guide.
Building web scrapers or data pipelines? Check out my Apify actors for ready-made solutions, or email spinov001@gmail.com for custom development.
Top comments (0)