A structured Go framework for building efficient, scalable, and production server-side applications.
| Explicit over implicit | No magic. Every dependency is wired by hand. |
| Errors as values | Handlers return Response. No side effects, no panic. |
| Structure at scale | Module-based organization. Enforced by the framework. |
| Stdlib at the boundary | Bast satisfies http.Handler. The full Go ecosystem works. |
| Lean core | Near-zero dependencies. Batteries are opt-in companion packages. |
- Radix tree router — zero allocations on the hot path, static-wins-over-param priority
- Pooled
*Ctx—sync.Poolbased, 0 allocs per request for routing and context - Module system — portable, self-contained units with dependency injection at
main.go - Typed error boundary — one
ErrorHandler, consistent JSON envelopes everywhere - Built-in middleware —
RequestID,Logger,Recover,CORS - Guards — pre-handler checks with
SecuredGuard→ OpenAPI security scheme auto-wiring - Streaming —
*StreamCtxfor SSE and chunked responses, never pooled - Health checks —
/health(liveness) and/ready(readiness) with dependency checks - OpenAPI 3.0 — spec generated from code at startup; Swagger UI at
/docs - Typed env config —
LoadConfig[T]()withenv,default,required,secrettags - Logger — colored
[Bast]boot and request logs, fully pluggable bast newCLI — scaffold a working Todo API in secondsbasttest— unit and integration test helpers built into the framework
# Install the CLI
go install github.com/bastion-framework/bast/cmd/bast@latest
# Scaffold a new project (generates a working Todo API)
bast new myapp
cd myapp
# Wire the local framework and run
go mod tidy
go run .Open http://localhost:8080/docs for the Swagger UI.
go get github.com/bastion-framework/bast@latestRequires Go 1.22+.
bast new <appname> # Scaffold a new project
bast generate module <name> # Generate a module (5 files)
bast generate guard <name> # Generate a guard
bast generate service <name> # Generate a shared service
bast run # Run the app
bast run --watch # Run + rebuild/restart on file changes
bast build # Production binary → bin/<app>
bast build --os linux --arch arm64 # Cross-compilebast build produces deployment-ready binaries: reproducible (-trimpath),
stripped (-s -w), statically linked (CGO_ENABLED=0) — runs in scratch
and distroless containers.
basttest ships with the framework — no separate install needed.
// Unit test — handler as a pure function
func TestGetUser(t *testing.T) {
ctx := basttest.NewCtx(
basttest.WithParam("id", "42"),
basttest.WithStore("claims", &Claims{Role: "admin"}),
)
resp := controller.GetUser(ctx)
assert.Equal(t, 200, resp.Status())
}
// Integration test — full request lifecycle
func TestCreateUser(t *testing.T) {
app := basttest.NewApp(users.NewModule(testDB))
app.Do("POST", "/users",
basttest.WithJSONBody(CreateUserRequest{Name: "Kasim", Email: "k@suds.ug"}),
).Assert(t).StatusIs(201).BodyContains("id")
}Intel i7-9700K @ 3.60 GHz, Go 1.25, windows/amd64. Three benchmark tiers — run them all with:
cd bench && go test -bench=. -benchmem -benchtime=5s
Measures pure route-matching time by calling the router's lookup API directly. Bast's flat-arena BFS layout outperforms httprouter on non-trivial routes — and never allocates for path parameters.
| Benchmark | bast | httprouter |
|---|---|---|
Static GET /ping |
13 ns · 0 allocs | 12 ns · 0 allocs |
Param GET /users/:id |
22 ns · 0 allocs | 48 ns · 1 alloc |
| GitHub corpus (26 routes, 8 requests) | 32 ns · 0 allocs | 59 ns · 0 allocs |
Every framework returns status 200 with no body and no extra headers — the same workload gin and echo use in their own published benchmarks. This isolates routing + dispatch from response-writing.
| Framework | ns/op | allocs/op |
|---|---|---|
| gin | 60 | 0 |
| httprouter | 67 | 0 |
| echo | 84 | 0 |
| bast | 139 | 0 |
| chi | 526 | 3 |
| gorilla/mux | 1 618 | 7 |
| Framework | ns/op | allocs/op |
|---|---|---|
| httprouter | 20 | 0 |
| gin | 37 | 0 |
| echo | 38 | 0 |
| bast | 110 | 0 |
| chi | 284 | 2 |
| gorilla/mux | 656 | 7 |
Bast uses a realistic handler (ctx.OK(nil)) that writes a proper JSON envelope with Content-Type: application/json. Other frameworks still use their minimum-work handler.
| Benchmark | bast | gin | echo | httprouter | iris | stdlib | chi | gorilla/mux |
|---|---|---|---|---|---|---|---|---|
| GitHub corpus | 219 ns · 0 allocs | 59 | 83 | 66 | 178 | 298 | 519 | 1 548 |
| Static | 177 ns · 0 allocs | 37 | 42 | 20 | 83 | 92 | 284 | 668 |
| Param | 190 ns · 0 allocs | 46 | 47 | 56 | 146 | 182 | 331 | 874 |
Fiber (fasthttp) is excluded — its
app.Test()harness pipes a full HTTP/1.1 message in-process (~7 µs overhead absent in production).
Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request.
Bast is MIT licensed.
Author Kasim Lyee
