BuildBot

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.

The Go↔JS bridge (JS model)

Run it. invoke() routes a method name to its handler, mirroring how Wails calls bound Go methods from the frontend.

Loading editor…
Knowledge check

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.