Skip to content

Writing Handlers

GoMode uses a reactor model: main() runs once to initialize the Go runtime, then handle_zerobuf() is called for each request. The WASM instance stays alive.

func main() {
// Initialize anything global here.
// This runs once when WASM is first loaded.
}
//export handle_zerobuf
func handleZerobuf(reqBase uint32) uint32 {
// Called for every request.
// reqBase points to zerobuf tagged values in WASM memory.
}

The Worker writes request fields as zerobuf tagged value slots directly into WASM memory:

  • Field 0: method (string) at reqBase + 0*16
  • Field 1: path (string) at reqBase + 1*16

Read with the zerobuf helpers:

reqAddr := uintptr(reqBase)
method := readZBString(reqAddr + 0*16)
path := readZBString(reqAddr + 1*16)

Write response fields as zerobuf tagged values and return the pointer:

func writeResponse(status int32, contentType string, body string) uint32 {
resp := make([]byte, 3*16) // 3 fields × 16 bytes
respStrings = respStrings[:0]
respBase = uintptr(unsafe.Pointer(&resp[0]))
writeZBI32(respBase+0*16, status)
writeZBString(respBase+1*16, contentType)
writeZBString(respBase+2*16, body)
return uint32(respBase)
}

Response fields:

  • Field 0: status (i32) at respBase + 0*16
  • Field 1: contentType (string) at respBase + 1*16
  • Field 2: body (string) at respBase + 2*16

Import the gomode package and call Zig functions directly:

import "gomode"
func handleSimd() uint32 {
data := []float64{1, 2, 3, 4, 5, 6, 7, 8}
ptr := uint32(uintptr(unsafe.Pointer(&data[0])))
count := uint32(len(data))
sum := gomode.ZigSimdSumF64(ptr, count)
dot := gomode.ZigSimdDotF64(ptr, ptr, count)
gomode.ZigSimdScaleF64(ptr, count, 2.0)
return writeResponse(200, "text/plain", formatFloat(sum))
}

These compile to direct WASM call instructions — zero overhead.

Route by path in your handler:

switch path {
case "/":
return writeResponse(200, "text/plain", "Hello!")
case "/simd":
return handleSimd()
case "/json":
return writeResponse(200, "application/json", `{"ok":true}`)
default:
return writeResponse(404, "text/plain", "not found")
}
  • -gc=leaking — Never frees memory. Smallest binary (79KB). Fine for short-lived or low-traffic handlers.
  • -gc=conservative — Actual garbage collection. Use for long-lived DO instances with many requests.

Build with:

Terminal window
tinygo build -target wasip1 -scheduler=none -gc=leaking \
-ldflags="-extldflags='build/zig-abi.o'" \
-o build/go.wasm .