BESPA is a Go library — no toolchain, no bundler, no separate frontend repo. Add one go get to your project, write a handler that returns a tree of widgets, and serve it. This page walks through the smallest end-to-end example.
Add the module to your project:
go get github.com/microbus-io/bespa
Put this in main.go. It’s a complete program — a name input and a greeting that updates as you type:
package main
import (
"net/http"
"github.com/microbus-io/bespa"
"github.com/microbus-io/bespa/widget"
)
var wf = bespa.DefaultFactory{}
func handleHome(w http.ResponseWriter, r *http.Request) {
state := wf.StateOf(r)
page := wf.Page().Add(
wf.AppBar("Hello"),
wf.Form().Add(
wf.InputText("name", "").
WithPlaceholder("Your name").
WithAutoSubmit(true),
),
wf.HeadlineMedium("Hello, ", state.Get("name"), "!").
HideIfEmpty(r, "name").
RedrawIfChanged(r, "name"),
)
page.Draw(w, r)
}
func main() {
http.HandleFunc("/bespa/", widget.AssetRegistry.ServeHTTP)
http.HandleFunc("/", handleHome)
http.ListenAndServe(":8080", nil)
}
Then run it and open the page:
go run .
Visit http://localhost:8080. Type your name. Notice the greeting updates without a full page reload, the URL changes to ?name=…, and the cursor stays in the input. That’s the entire BESPA loop: state in the URL, server re-renders affected widgets, the client swaps fragments in place.
Two lines in main matter:
http.HandleFunc("/bespa/", widget.AssetRegistry.ServeHTTP) — required. The framework serves its CSS, JavaScript, fonts, and any widget assets out of this prefix. Without it your <link> and <script> tags 404.var wf = bespa.DefaultFactory{} — the bag of widgets you already have. wf.Page, wf.Form, wf.InputText, wf.HeadlineMedium, etc., all come from this one value.A second handler shows the core idiom: every link is a state change, every widget that depends on the state opts in to redraw, and nothing else moves. A counter:
func handleCounter(w http.ResponseWriter, r *http.Request) {
state := wf.StateOf(r)
count, _ := strconv.Atoi(state.Get("count"))
wf.Page().Add(
wf.AppBar("Counter"),
wf.HeadlineLarge(count).
RedrawIfChanged(r, "count"),
wf.Toolbar().AddLeft(
wf.ButtonFilled("").Add("-").
WithHref("?count=" + strconv.Itoa(count-1)).
RedrawIfChanged(r, "count"),
wf.ButtonFilled("").Add("+").
WithHref("?count=" + strconv.Itoa(count+1)).
RedrawIfChanged(r, "count"),
),
).Draw(w, r)
}
Click + or − and only the heading and the two buttons (whose hrefs change) re-render. The toolbar, AppBar, and the rest of the page stay put. See Basics → Incremental updates for the underlying protocol.
bespa.DefaultFactory gives you everything in basic/, form/, table/, and nav/ — the standard universe. Heavier widgets are opt-in packages: chart (Apache ECharts), code (Chroma syntax highlighting), richedit (Quill 2). Compose whichever you need into a single factory:
import (
"github.com/microbus-io/bespa"
"github.com/microbus-io/bespa/chart"
"github.com/microbus-io/bespa/code"
)
// Compose the default widgets with optional packages and your own.
var wf = struct {
bespa.DefaultFactory
chart.ChartFactory
code.CodeFactory
// myorg.MyOrgFactory // your own widget library
}{}
Go’s embedded-struct method resolution does the rest — wf.Chart(...) and wf.CodeBlock(...) show up alongside wf.Page(...) with no conflict. See Extend → Packaging to write your own.