Tutorial: Zero to Hero SaaS
Build a fully functional Customer Feedback SaaS in 15 minutes using OmniSvelte.
Build a SaaS in 15 Minutes
Welcome to the OmniSvelte Zero-to-Hero tutorial! In this guide, we won't just build a toy app—we will build a Customer Feedback SaaS (think Canny or UserVoice) from scratch.
We will cover setting up your database, defining relationships, scaffolding the UI, securing API endpoints with authentication, and utilizing SvelteKit remote functions for real-time reactivity.
Prerequisites
Initialize your project
omni init feedback-saas --with-auth
cd feedback-saasHere's the structure of your newly created OmniSvelte project:
-
-
-
- schema.ts
- db.ts
-
- +page.svelte
-
- omni.config.ts
- package.json
-
Define your SaaS Schema & Relationships
import { defineSchema, field } from '@omni-svelte/core';
export const Product = defineSchema('products', {
name: field.string(),
slug: field.string().unique(),
});
export const Feedback = defineSchema('feedback', {
title: field.string(),
description: field.text(),
status: field.enum(['open', 'planned', 'shipped']).default('open'),
upvotes: field.integer().default(0),
productId: field.references(() => Product.id),
userId: field.references(() => User.id) // Automatically injected by BetterAuth
});The framework will automatically generate the database migrations, TypeScript types, and Zod validation schemas for you.
Generate the UI and APIs
omni generate resource Product
omni generate resource FeedbackThis single command wires up the backend APIs, generates accessible forms using Zod, and creates the frontend views using shadcn-svelte.
Secure your Remote Functions
import { command } from '@omni-svelte/core/remote';
import { auth } from '$auth/server';
import { Feedback } from '$models/Feedback';
export const createFeedback = command()
.schema(Feedback.insertSchema)
.use(async ({ request, locals }) => {
// Middleware: Ensure user is logged in
const session = await auth.api.getSession({ headers: request.headers });
if (!session) throw new Error("Unauthorized");
locals.user = session.user;
})
.handler(async ({ input, locals }) => {
// Handler: Create the feedback in the DB
return await Feedback.create({
...input,
userId: locals.user.id
});
});Build a Real-Time Feed
<script lang="ts">
import { getFeedbackLive } from '$lib/server/feedback.remote';
let { data } = $props();
// This store will automatically update when anyone submits new feedback!
const feedbackFeed = getFeedbackLive({ productId: data.product.id });
</script>
<div class="space-y-4">
{#each $feedbackFeed as item}
<div class="p-4 border rounded-md shadow-sm">
<h3 class="font-bold">{item.title}</h3>
<p class="text-sm text-muted-foreground">{item.description}</p>
<span class="text-xs bg-primary/10 text-primary px-2 py-1 rounded-full">
{item.status}
</span>
</div>
{/each}
</div>Add dark mode
We really need a dark mode for late night coding.
plannedFaster search
Search takes too long on large tables.
openRun the application
omni db push
npm run dev