# JavaScript syscall/js Utilities for Go WASM Development: A Complete Guide

> Explore aperturerobotics/util for Go WASM development. Easily make HTTP requests and handle streams in WebAssembly using lightweight syscall/js JavaScript utilities. No net/http needed.

- Repository: [Aperture Robotics/util](https://github.com/aperturerobotics/util)
- Tags: how-to-guide
- Published: 2026-02-25

---

**The aperturerobotics/util repository provides lightweight Go packages that wrap browser JavaScript APIs via syscall/js, enabling HTTP requests and stream handling in WebAssembly without importing net/http.**

When compiling Go to WebAssembly (WASM), the standard `net/http` package is unavailable, forcing developers to bridge Go and JavaScript manually using the `syscall/js` package. The **aperturerobotics/util** repository solves this with a focused set of **JavaScript syscall/js utilities** that expose the browser's native Fetch API and ReadableStream objects to Go code. These wrappers handle the complex promise-to-channel synchronization and byte-array copying required for seamless WASM networking.

## Core syscall/js Utilities in aperturerobotics/util

The repository's `js/` directory contains four primary utilities that work together to provide HTTP client functionality in WASM environments.

### Fetch API Wrapper (js/fetch)

The `js/fetch` package provides a thin, idiomatic Go interface to the browser's `fetch()` function. Located in [[`js/fetch/fetch.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/fetch.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/fetch.go), the `fetch.Fetch()` function constructs request options, invokes `js.Global().Call("fetch", ...)`, and translates the JavaScript `Response` object into a Go `*Response` struct implementing `io.ReadCloser`.

The wrapper handles promise resolution by attaching `then` and `catch` callbacks using `js.FuncOf`, pushing results onto a Go channel to bridge JavaScript's asynchronous model with Go's synchronous execution flow.

### HTTP Header Management

The [[`js/fetch/header.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/header.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/header.go) file defines a `Header` type that mirrors the standard `net/http.Header` interface while working with JavaScript objects. It provides methods including `Add()`, `Set()`, `Get()`, `Clone()`, and `Write()`, along with canonicalization logic to normalize header names (e.g., converting "content-type" to "Content-Type").

This allows WASM applications to construct and parse HTTP headers using familiar Go patterns while internally converting to JavaScript-compatible objects via `syscall/js`.

### Fetch Configuration Enums

The [[`js/fetch/enums.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/enums.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/enums.go) file exports string constants for all Fetch API configuration options. These include HTTP methods (`MethodGet`, `MethodPost`, etc.), cache modes, credentials modes, request modes, redirect behaviors, and referrer policies.

Using these typed constants prevents runtime errors from typos in string literals and provides IDE autocomplete for valid Fetch API values.

### ReadableStream Implementation (js/readable-stream)

The [[`js/readable-stream/stream.go`](https://github.com/aperturerobotics/util/blob/main/js/readable-stream/stream.go)](https://github.com/aperturerobotics/util/blob/master/js/readable-stream/stream.go) package implements `io.ReadCloser` on top of JavaScript `ReadableStream` objects. The `NewReadableStream()` function accepts a `js.Value` representing a stream and returns a Go type that can be used with standard library functions like `io.Copy()` or `bufio.NewReader()`.

Internally, the wrapper manages the asynchronous `reader.read()` promise, buffers incoming `Uint8Array` chunks using `js.CopyBytesToGo`, and implements a "prefixedReader" shim to handle any bytes peeked during stream validation. It also properly closes the stream and releases JavaScript resources when `Close()` is called.

## How the Utilities Work Together

These **JavaScript syscall/js utilities** combine to provide a complete HTTP client workflow:

1. **Request Construction**: Create `fetch.Opts` embedding `CommonOpts` for cache, credentials, and mode settings. Attach a `fetch.Header` map for metadata and provide an `io.Reader` body if needed.

2. **Promise Handling**: `fetch.Fetch` converts the options to a JavaScript object using `js.ValueOf`, creates an `AbortController` if cancellation is requested, and calls `js.Global().Call("fetch", url, mapOpts)`. It attaches callbacks via `js.FuncOf` that push results onto a Go channel.

3. **Response Parsing**: The success callback extracts status codes, headers (using the JS iterator protocol on `response.headers.entries()`), and the response URL from the JavaScript `Response` object.

4. **Body Streaming**: If the response contains a non-null `body`, the wrapper creates a `stream.NewReadableStream(body)` which implements `io.ReadCloser`. This allows standard Go I/O operations on the streaming response body.

5. **Cancellation**: When an `opts.Signal` (`context.Context`) is provided, the wrapper creates a JavaScript `AbortController`, attaches its signal to the fetch request, and uses `context.AfterFunc` to call `controller.Call("abort")` if the context is cancelled.

## Practical Code Examples

### Simple GET Request with Timeout

This example demonstrates a basic fetch with context-based cancellation, using the `js/fetch` package to avoid the unavailable `net/http` client in WASM.

```go
package main

import (
	"context"
	"fmt"
	"io"
	"time"

	"github.com/aperturerobotics/util/js/fetch"
)

func main() {
	// Context with timeout – aborts the fetch if it takes too long.
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Build options – we only need the cancellation signal here.
	opts := &fetch.Opts{
		Method: fetch.MethodGet,
		Signal: ctx,
	}

	// Perform the request.
	resp, err := fetch.Fetch("https://httpbin.org/get", opts)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	// Read the whole body.
	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Status: %d\nBody: %s\n", resp.StatusCode, body)
}

```

Key implementation details from [[`js/fetch/fetch.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/fetch.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/fetch.go): `fetch.Fetch` invokes `js.Global().Call("fetch", ...)` and translates the JavaScript `Response` into a Go `*Response` implementing `io.ReadCloser`.

### POST Request with JSON Payload

This example shows how to send structured data using the `Header` type for content negotiation and the `Body` field for the request payload.

```go
package main

import (
	"context"
	"encoding/json"
	"io"
	"strings"
	"time"

	"github.com/aperturerobotics/util/js/fetch"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	payload := `{"msg":"hello"}`
	opts := &fetch.Opts{
		Method: fetch.MethodPost,
		Signal: ctx,
		Body:   strings.NewReader(payload),
		Header: fetch.Header{
			"Content-Type": []string{"application/json"},
		},
	}

	resp, err := fetch.Fetch("https://httpbin.org/post", opts)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	b, _ := io.ReadAll(resp.Body)

	var out map[string]any
	_ = json.Unmarshal(b, &out)
	// `out["json"]` now holds the parsed request body.
}

```

Implementation highlights from [[`js/fetch/header.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/header.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/header.go): The `Header` type automatically canonicalizes keys (e.g., "content-type" to "Content-Type") and provides the standard `Add`, `Set`, and `Get` methods familiar from `net/http`.

### Direct ReadableStream Usage

When integrating with existing JavaScript libraries that return `ReadableStream` objects, the `js/readable-stream` package provides direct conversion to Go's `io.ReadCloser`.

```go
import (
	"github.com/aperturerobotics/util/js/readable-stream"
	"syscall/js"
)

func streamFromJS(jsStream js.Value) io.ReadCloser {
	return stream.NewReadableStream(jsStream)
}

```

Now you can use standard Go I/O operations:

```go
reader := stream.NewReadableStream(jsStream)
bufReader := bufio.NewReader(reader)
line, err := bufReader.ReadString('\n')

```

From [[`js/readable-stream/stream.go`](https://github.com/aperturerobotics/util/blob/main/js/readable-stream/stream.go)](https://github.com/aperturerobotics/util/blob/master/js/readable-stream/stream.go): The implementation manages the asynchronous `reader.read()` promise, buffers `Uint8Array` chunks using `js.CopyBytesToGo`, and includes a "prefixedReader" shim to handle bytes peeked during validation.

## Key Source Files and Architecture

The **JavaScript syscall/js utilities** in aperturerobotics/util are organized under the `js/` directory with clear separation of concerns:

| File | Purpose |
|------|---------|
| [[`js/fetch/fetch.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/fetch.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/fetch.go) | Core fetch wrapper – builds options, invokes the JS Fetch API, handles promises via channels, and translates Response objects. |
| [[`js/fetch/header.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/header.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/header.go) | HTTP Header map implementation with canonicalization, compatible with `net/http.Header` patterns. |
| [[`js/fetch/enums.go`](https://github.com/aperturerobotics/util/blob/main/js/fetch/enums.go)](https://github.com/aperturerobotics/util/blob/master/js/fetch/enums.go) | Typed constants for Fetch API options (methods, cache modes, credentials, redirects). |
| [[`js/readable-stream/stream.go`](https://github.com/aperturerobotics/util/blob/main/js/readable-stream/stream.go)](https://github.com/aperturerobotics/util/blob/master/js/readable-stream/stream.go) | `io.ReadCloser` implementation over JavaScript ReadableStream with async read management. |
| [[`README.md`](https://github.com/aperturerobotics/util/blob/main/README.md)](https://github.com/aperturerobotics/util/blob/master/README.md) | Repository documentation listing available syscall/js utilities. |

These files work together to provide a **native-like HTTP client** for Go-WASM that operates entirely within the browser environment, avoiding the unsupported `net/http` package.

## Summary

- The **aperturerobotics/util** repository provides essential **JavaScript syscall/js utilities** for Go WebAssembly development, specifically targeting HTTP networking and stream handling.
- The `js/fetch` package wraps the browser's native Fetch API, offering a Go-idiomatic interface with support for contexts, cancellation, and standard HTTP headers.
- The `js/readable-stream` package bridges JavaScript ReadableStream objects to Go's `io.ReadCloser`, enabling standard library I/O operations on streaming data.
- All utilities avoid the unsupported `net/http` package, instead using `syscall/js` to interact directly with browser APIs via `js.Global().Call()`, `js.FuncOf()`, and `js.CopyBytesToGo()`.

## Frequently Asked Questions

### How do these utilities differ from using net/http in Go?

Standard Go applications use `net/http` for HTTP requests, but this package is not available when compiling to WebAssembly because it relies on low-level network primitives that browsers do not expose. The **JavaScript syscall/js utilities** in aperturerobotics/util instead call the browser's native Fetch API directly via `syscall/js`, providing equivalent functionality while remaining compatible with WASM targets.

### Can I use context cancellation with fetch requests?

Yes. The `fetch.Opts` struct accepts a `Signal` field that takes a `context.Context`. When provided, the wrapper creates a JavaScript `AbortController` and attaches its signal to the fetch request. If the context is cancelled or times out, the wrapper automatically calls `controller.Call("abort")`, terminating the in-flight request and returning the context error to Go code.

### How does the ReadableStream wrapper handle backpressure?

The `js/readable-stream` implementation manages backpressure by controlling the JavaScript reader's `read()` promise lifecycle. It buffers incoming `Uint8Array` chunks using `js.CopyBytesToGo` and only requests the next chunk when the Go side consumes the current buffer via `Read()`. The wrapper also implements a "prefixedReader" pattern to handle any bytes peeked during stream validation, ensuring no data is lost during the transition from JavaScript streams to Go `io.Reader` semantics.