Building Wasm Components with Go
This cookbook guide shows you how to build WebAssembly components using Go and TinyGo that work with Wassette.
Quick Start
Prerequisites
- Go (version 1.19 through 1.23)
- TinyGo (version 0.32 or later)
Install Tools
# Install Go from https://golang.org/dl/
# Install TinyGo from https://tinygo.org/getting-started/install/
# On macOS with Homebrew:
brew tap tinygo-org/tools
brew install tinygo
# On Linux:
wget https://github.com/tinygo-org/tinygo/releases/download/v0.32.0/tinygo_0.32.0_amd64.deb
sudo dpkg -i tinygo_0.32.0_amd64.deb
Step-by-Step Guide
1. Create Your Project
mkdir my-component
cd my-component
go mod init example.com/my-component
2. Define Your Interface (WIT)
Create wit/world.wit
:
package local:my-component;
world my-component {
export greet: func(name: string) -> string;
export calculate: func(a: s32, b: s32) -> s32;
}
3. Generate Go Bindings
go run go.bytecodealliance.org/cmd/wit-bindgen-go@v0.6.2 generate ./wit --out gen
This creates Go bindings in the gen/
directory.
4. Implement Your Component
Create main.go
:
package main
import (
"fmt"
"example.com/my-component/gen"
)
// Component implementation
type Component struct{}
func (c Component) Greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
func (c Component) Calculate(a, b int32) int32 {
return a + b
}
func init() {
// Export the component
gen.SetExports(Component{})
}
func main() {}
5. Build Your Component
tinygo build -o component.wasm -target wasip2 --wit-package ./wit --wit-world my-component main.go
6. Test Your Component
wassette serve --sse --plugin-dir .
Complete Examples
Module Information Service
wit/world.wit:
package local:gomodule-server;
world gomodule-server {
export get-module-info: func(module-path: string) -> result<string, string>;
}
main.go:
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
"example.com/gomodule-server/gen"
)
type ModuleInfo struct {
Path string `json:"path"`
Version string `json:"version"`
Time string `json:"time"`
}
type Component struct{}
func (c Component) GetModuleInfo(modulePath string) (string, error) {
// Fetch module information from pkg.go.dev
url := fmt.Sprintf("https://proxy.golang.org/%s/@latest", modulePath)
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("failed to fetch: %v", err)
}
defer resp.Body.Close()
var info ModuleInfo
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return "", fmt.Errorf("failed to parse: %v", err)
}
result, err := json.Marshal(info)
if err != nil {
return "", fmt.Errorf("failed to marshal: %v", err)
}
return string(result), nil
}
func init() {
gen.SetExports(Component{})
}
func main() {}
Simple Calculator
wit/world.wit:
package local:calculator;
world calculator {
export add: func(a: s32, b: s32) -> s32;
export subtract: func(a: s32, b: s32) -> s32;
export multiply: func(a: s32, b: s32) -> s32;
export divide: func(a: s32, b: s32) -> result<s32, string>;
}
main.go:
package main
import (
"fmt"
"example.com/calculator/gen"
)
type Calculator struct{}
func (c Calculator) Add(a, b int32) int32 {
return a + b
}
func (c Calculator) Subtract(a, b int32) int32 {
return a - b
}
func (c Calculator) Multiply(a, b int32) int32 {
return a * b
}
func (c Calculator) Divide(a, b int32) (int32, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func init() {
gen.SetExports(Calculator{})
}
func main() {}
Text Processing
wit/world.wit:
package local:text-processor;
world processor {
export process-text: func(input: string, operation: string) -> result<string, string>;
}
main.go:
package main
import (
"fmt"
"strings"
"example.com/text-processor/gen"
)
type Processor struct{}
func (p Processor) ProcessText(input, operation string) (string, error) {
switch operation {
case "uppercase":
return strings.ToUpper(input), nil
case "lowercase":
return strings.ToLower(input), nil
case "reverse":
runes := []rune(input)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes), nil
default:
return "", fmt.Errorf("unknown operation: %s", operation)
}
}
func init() {
gen.SetExports(Processor{})
}
func main() {}
Error Handling
Go components use the standard Go error type for WIT’s result
:
// Success
return result, nil
// Error
return "", fmt.Errorf("error message: %v", err)
// Or with a zero value for the result
return 0, fmt.Errorf("calculation failed")
Working with WIT Types
Type Mappings
// WIT type -> Go type
// s32, s64 -> int32, int64
// u32, u64 -> uint32, uint64
// f32, f64 -> float32, float64
// string -> string
// bool -> bool
// list<T> -> []T
// option<T> -> *T
// result<T, E> -> (T, error)
// record -> struct
Complex Types
// WIT record
// record person {
// name: string,
// age: u32,
// }
type Person struct {
Name string
Age uint32
}
func processPerson(p Person) string {
return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
Best Practices
- Use proper error handling - Always check and return errors appropriately
- Keep components small - TinyGo produces smaller binaries with focused code
- Avoid unsupported features - Some Go standard library features may not work with TinyGo
- Test thoroughly - Validate your component works with Wassette before deployment
- Document interfaces - Use WIT comments to document your API
Common Patterns
JSON Processing
import (
"encoding/json"
"fmt"
)
type Data struct {
Name string `json:"name"`
Value int `json:"value"`
}
func processJSON(jsonStr string) (string, error) {
var data Data
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
return "", fmt.Errorf("invalid JSON: %v", err)
}
// Process data
data.Value *= 2
result, err := json.Marshal(data)
if err != nil {
return "", fmt.Errorf("marshal error: %v", err)
}
return string(result), nil
}
HTTP Client
import (
"fmt"
"io"
"net/http"
)
func fetchURL(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("bad status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("read failed: %v", err)
}
return string(body), nil
}
String Validation
import (
"fmt"
"strings"
)
func validateInput(input string) (string, error) {
if strings.TrimSpace(input) == "" {
return "", fmt.Errorf("input cannot be empty")
}
if len(input) > 1000 {
return "", fmt.Errorf("input too long (max 1000 chars)")
}
return strings.TrimSpace(input), nil
}
Build Configuration
Using Justfile
Create Justfile
for build automation:
# Generate Go bindings from WIT files
generate:
go run go.bytecodealliance.org/cmd/wit-bindgen-go@v0.6.2 generate ./wit --out gen
# Build the component
build: generate
tinygo build -o component.wasm -target wasip2 --wit-package ./wit --wit-world my-component main.go
# Build with optimizations
build-release: generate
tinygo build -o component.wasm -target wasip2 --wit-package ./wit --wit-world my-component -opt=2 main.go
# Clean build artifacts
clean:
rm -rf gen/
rm -f component.wasm
# Test the component
test: build
wassette serve --sse --plugin-dir .
Usage:
just build # Build component
just build-release # Build with optimizations
just clean # Clean artifacts
Troubleshooting
Build Errors
TinyGo version issues:
- Ensure TinyGo supports your Go version (currently 1.19-1.23)
- Update TinyGo to the latest version
WIT binding errors:
- Regenerate bindings after WIT changes
- Check that wit-bindgen-go version is compatible
Import errors:
- Some Go packages may not work with TinyGo
- Use TinyGo-compatible alternatives or implement manually
Runtime Errors
Component not loading:
- Verify WIT interface matches implementation
- Check that all exported functions are implemented
- Review Wassette logs for details
Performance issues:
- Use
-opt=2
flag for optimized builds - Profile your code to find bottlenecks
- Consider using Rust for performance-critical components
TinyGo Limitations
Some Go features are not available in TinyGo:
- Some reflection features
- Full goroutine support (limited)
- Some standard library packages
- CGO
See TinyGo language support for details.
Full Documentation
For complete details, including advanced topics and more examples, see the Go Development Guide.
Working Examples
See this complete working example in the repository:
- gomodule-go - Go module information service
Next Steps
- Review the complete Go guide
- Check out working examples
- Learn about TinyGo
- Read the FAQ