Go Fiber
Fast HTTP, single binary, no runtime to deploy. The foundation everything else builds on.
Every choice in Laju Go is a bet against complexity. The goal is a SaaS you can understand entirely in an afternoon, deploy to a single VPS, and scale further than you’ll likely need — without reaching for Kubernetes, a separate API service, or an ORM.
Go compiles to a single static binary. No runtime to install on the server, no node_modules in production, no JIT warmup. You scp one file and run it.
Fiber sits on top of fasthttp, not the standard net/http, which makes it one of the fastest Go web frameworks available. For a SaaS starter, that is headroom you will never have to worry about.
The trade-off: CGO. SQLite uses go-sqlite3, which is CGO-based, so cross-compilation needs a C toolchain. Laju Go solves this with a make build-linux target that uses Zig as the cross-compiler — one command, no manual CC wiring.
make build-linux # GOOS=linux GOARCH=amd64, CGO via ZigPostgres is excellent. It is also a second process to run, monitor, back up, and connect to. For the vast majority of SaaS starters — and honestly, for a surprising number of production workloads — SQLite is enough.
Laju Go runs SQLite in WAL mode with tuned pragmas for concurrency:
db.SetMaxOpenConns(1) // SQLite writes are serializeddb.Exec("PRAGMA journal_mode=WAL") // concurrent readers + one writerdb.Exec("PRAGMA busy_timeout=5000")db.Exec("PRAGMA foreign_keys=ON")db.Exec("PRAGMA synchronous=NORMAL")What you get:
data/app.db). Copy it. Done.When you outgrow SQLite — and you may never — migrating to Postgres is a matter of swapping the sqlc engine and the connection string. The query layer stays the same because sqlc abstracts it.
ORMs trade type safety for convenience, and you pay for it at runtime: N+1 queries you didn’t see, lazy-load surprises, generated SQL you can’t read. sqlc inverts the trade.
You write plain SQL — the thing you already know, the thing that maps 1:1 to what the database actually does:
-- name: GetUserByEmail :oneSELECT id, email, name, password, avatar, role, google_id, email_verified, created_at, updated_atFROM usersWHERE email = ?;sqlc generates fully typed Go:
// Generated by sqlc. DO NOT EDIT.func (q *Querier) GetUserByEmail(ctx context.Context, email string) (User, error) { ... }The benefits:
sqlc generate fails. If you rename a struct field, the compiler catches every call site.database/sql calls — fast and debuggable.queries/. No hidden query generation.The trade-off: you write SQL instead of Go method chains. If you find that trade painful, this is not your stack.
The conventional SPA architecture is two apps: a frontend (React/Vue/Svelte) and a separate REST/GraphQL API. You build the API, you build the client, you keep their types in sync, you handle auth twice, you version both. It doubles the surface area.
Inertia.js collapses them. The Go server renders Svelte components directly — the same way it would render an HTML template — but the result is a SPA. There is no separate API. The server is the API.
func (h *AppHandler) Dashboard(c *fiber.Ctx) error { sess, _ := h.store.Get(c) user := sessionUser(sess)
return h.inertiaService.Render(c, "app/Dashboard", fiber.Map{ "user": user, })}On the frontend, useForm + <form> handles CRUD without fetch boilerplate:
<script> import { useForm } from "@inertiajs/svelte"; const form = useForm({ name: "", email: "" }); function submit() { form.post("/register"); }</script>
<form onsubmit={submit}> <input name="name" bind:value={form.fields.name} /> <input name="email" bind:value={form.fields.email} /> <button>Register</button></form>What you get:
useForm.The trade-off: your frontend and backend are coupled. If you need a public mobile API, you’ll build it separately. For a SaaS web app, the coupling is the feature.
Go’s html/template is stringly-typed — a typo in a template name or a missing field fails at runtime. templ fixes that by compiling templates to Go code.
You write .templ files with a clean syntax:
templ Dashboard(user *models.UserResponse) { <div class="dashboard"> <h1>Welcome, { user.Name }</h1> </div>}templ generate produces Go functions that are fully typed — pass the wrong type and the compiler stops you. No more runtime template errors.
The trade-off: it is a code generation step (templ generate), and Air does not watch .templ files by default, so you regenerate manually after edits. A small price for compile-time HTML safety.
Go Fiber
Fast HTTP, single binary, no runtime to deploy. The foundation everything else builds on.
SQLite
One file, zero ops, WAL-tuned for concurrency. Embedded in the binary’s deployment.
sqlc
Type-safe Go from plain SQL. The database layer is readable SQL, not magic method chains.
Inertia.js
SPA without a separate API. The server renders Svelte; one codebase, one deploy.
templ
Type-safe HTML that compiles to Go. No runtime template errors.
Svelte 5
Runes-based reactivity that is simple to reason about. $derived over $effect.
Each piece was chosen to eliminate a category of complexity, not to add a feature. Together they form a stack where the fastest path is also the correct path — and where an AI agent following AGENTS.md produces code that fits the architecture by construction.
If those trade-offs match your problem, Laju Go gets you to a deployed, authenticated, uploading SaaS faster than assembling the pieces yourself. If they don’t, that’s useful to know now, too.