The Desktop App
Wails: Go backend, web UI, one binary
Lesson 3 of 10
What you'll learn
- Understand what Wails is and why a desktop app needs a native shell
- See how Go methods become callable from the React frontend
- Contrast direct bindings with the HTTP/fetch model you know from the web
A web app runs in a browser someone else controls. The Quorum node needs to open UDP sockets, read the filesystem, spawn llama.cpp, and listen on a local port — things a browser sandbox forbids. So it ships as a desktop app: a native window wrapping a web UI, with a real Go process behind it. Wails is the framework that glues those together.
The mental model: Wails compiles your Go code and a built web frontend into one binary. At runtime it opens an OS webview (the system's browser engine, not a bundled Chromium) for the UI, and runs your Go as the backend in the same process. The two halves talk over an in-process bridge — not a network.
Go methods, called from JavaScript
You expose Go to the frontend by attaching methods to a bound struct. Wails generates TypeScript wrappers so the frontend calls them like async functions.
// apps/desktop/app.go
type App struct{ ctx context.Context }
// Exported methods on the bound struct are callable from JS.
func (a *App) ListNodes() []Node {
return cluster.Snapshot()
}
// frontend — calling the Go method (generated binding)
import { ListNodes } from "../wailsjs/go/main/App";
const nodes = await ListNodes(); // runs Go, returns JSON-marshalled result
No fetch, no URL, no JSON parsing by hand. The call crosses from the webview into Go, the return value is marshalled back, and you get a typed Promise. Events flow the other way too: Go can runtime.EventsEmit(ctx, "node:online", n) and the frontend subscribes — that's how live presence reaches the UI without polling.
One process, two languages
Because Go and the UI share a process, there's no auth, no CORS, no localhost port for the UI-to-core path. That private bridge is different from the OpenAI API the node serves to other programs, which is a real HTTP server on :32768.
The challenge models the bridge: a dispatcher that maps method names to Go-like handlers, exactly what the generated bindings do under the hood.
Run it. invoke() routes a method name to its handler, mirroring how Wails calls bound Go methods from the frontend.
How does the Wails frontend call the Go backend for UI data?
Next: what that frontend actually renders — the local control plane built with React, Vite, and Tailwind.
Saved on this device. Sign in to sync your progress everywhere.