Gothic Framework G symbol

HTTP & Files

Fetch makes HTTP requests straight from your WASM component and returns a Response — with the status code, headers, and body. GetFileBytes reads a file the user picked. TriggerDownload pushes a file back to the browser.

Important: You must dot-import core/wasm (import . "github.com/gothicframework/core/wasm") to use these helpers, otherwise you will get a tinygo compile error.

Note: these calls are blocking. Go's goroutine model handles concurrency for you, so always call them inside a goroutine or a CreateWasmFunc handler.

// Inside CreateWasmFunc — already runs in its own goroutine
CreateWasmFunc("load", func() {
	resp, err := Fetch("https://api.example.com/data")
	if err != nil {
		SetText("result", "error: "+err.Error())
		return
	}
	SetText("result", resp.Text())
})

// On mount — use the go keyword so the blocking call never freezes the WASM thread
go func() {
	resp, err := Fetch("https://api.example.com/data")
	if err == nil {
		SetText("result", resp.Text())
	}
}()

The Response carries everything about the reply. Read resp.Status, check resp.OK() (true for 200–299), inspect resp.Headers, and get the body as resp.Text() (string) or resp.Bytes() (raw bytes).

// Response{ Status int; Headers map[string]string; Body []byte }
CreateWasmFunc("load", func() {
	resp, err := Fetch("https://api.example.com/todos/1")
	if err != nil {
		SetText("result", "error: "+err.Error())
		return
	}

	SetText("status", strconv.Itoa(resp.Status))          // "200"
	SetText("ok", strconv.FormatBool(resp.OK()))          // "true" for 200..299
	SetText("ctype", resp.Headers["content-type"])        // "application/json"
	SetText("result", resp.Text())                        // body as a string
})

Need more control? Pass a FetchConfig to set Method, Headers, Body, BodyBytes, or Query parameters.

CreateWasmFunc("submit", func() {
	resp, err := Fetch("https://api.example.com/todos", FetchConfig{
		Method:  "POST",
		Headers: map[string]string{"Content-Type": "application/json"},
		Body:    "{\"title\":\"buy milk\"}",
		Query:   map[string]string{"notify": "true"}, // ?notify=true
	})
	if err != nil {
		SetText("result", "error: "+err.Error())
		return
	}
	if resp.OK() {
		SetText("result", "created!")
	}
})

To handle uploads, read the user's selected file with GetFileBytes and send the []byte through BodyBytes.

CreateWasmFunc("uploadFile", func() {
	data := GetFileBytes("upload")
	if data == nil {
		SetText("status", "no file selected")
		return
	}
	resp, err := Fetch("/api/upload", FetchConfig{
		Method:    "POST",
		Headers:   map[string]string{"Content-Type": "application/octet-stream"},
		BodyBytes: data,
	})
	if err != nil || !resp.OK() {
		SetText("status", "upload failed")
		return
	}
	SetText("status", "uploaded!")
})

Going the other way? Read resp.Bytes() for binary data and hand it to TriggerDownload as a file. Migrating from before: Fetch used to return a plain string and there was a separate FetchBytes. Now Fetch always returns a Response — use .Text() for a string and .Bytes() for bytes. FetchBytes was removed.

CreateWasmFunc("downloadReport", func() {
	resp, err := Fetch("/api/report.csv")
	if err != nil {
		return
	}
	// resp.Bytes() returns the raw []byte body (read via arrayBuffer, so
	// binary payloads are never UTF-8 corrupted). FetchBytes no longer exists.
	TriggerDownload("report.csv", resp.Bytes(), "text/csv")
})

One request, one wait. Want to fire many at once, run a callback when it finishes, or cancel in-flight requests? Learn Async Fetch next!