Go + WASM: Run Native Code in Your Browser
I was thinking "Can I run native binaries in the browser?" Naturally, my mind jumped to languages like Go or Rust—languages built compiling down to something close to the metal (bin). But here's the twist: browsers don't speak Go or Rust. They speak JavaScript. Or at least, they used to. This is where WebAssembly comes in. So... What is WASM? WebAssembly (or WASM) is like a virtual CPU that runs inside your browser. It defines its own instruction set. It doesn't compile to x86 or ARM, but to its own binary format that the browser can understand and execute efficiently. Think of it like this: The browser downloads a .wasm file. That .wasm file contains instructions in a binary format. Those instructions describe how to operate on a stack—push, pop, add, branch, loop, etc. The browser then either interprets or compiles those instructions into real machine code. It’s kind of like Java’s JVM bytecode—but made for performance and for modern compilers. And unlike Java, it wasn’t designed in the 90s. But Why Should I Care? WebAssembly is fast. Like, really fast. Not "a little faster than JS"—but close-to-native fast. That means you can take a performance-critical app (say, image processing, or running emulators) and run it in the browser without killing the fan. The people behind WASM knew what they were doing. The spec was designed by folks who deeply understand compiler and CPU internals. So even though it’s portable and sandboxed, it can still get compiled to native CPU instructions quickly and efficiently. Wait, Is It Like Assembly? WASM borrows a lot of ideas from traditional assembly: It’s imperative: instructions run one after another. It has a stack: push values, pop them off, compute. It has branching, memory access, arithmetic—all the usual suspects. But it’s not tied to any real CPU. It’s not x86, not ARM, not RISC-V. It’s WASM’s own thing. That’s why you can compile languages like Rust, C/C++, or Go into WASM. These compilers already know how to emit instructions for different architectures—and WASM is just one more "target" for them. Is It Only for Compiling Native Code? Mostly, yes. That’s the main use case right now: compiling existing codebases written in C/C++/Rust/Go into something that runs in the browser. But there's also WASI (WebAssembly System Interface), which is trying to bring system-level APIs to WASM—so you can run WASM outside the browser, too. That’s where things get interesting. Now you can run sandboxed WASM modules on the server, inside a CLI tool, or as plugins. Think Docker containers, but even more lightweight. What Can You Build With It? Here’s where it gets fun. WebAssembly opens up a whole new set of use cases for the web (and beyond): Audio/video processing: things like FFmpeg or Audacity in the browser Games & emulators: NES, SNES, PS1 emulators running natively in JS environments Secure plugins: apps that allow user-contributed code (think OBS or VS Code extensions) Cross-platform apps: compile once, run everywhere—desktop, mobile, browser And if you're doing backend stuff, some platforms (like Wasmer or Wasmtime) let you run WASM on the server. That means you can build multi-tenant systems where user code runs isolated and safely. Step-by-Step Setup 1. Project Structure go-wasm ├─ Makefile ├─ client.html ├─ go.mod ├─ main.go ├─ main.wasm └─ wasm_exec.js 2. main.go: Export Go Function to JavaScript package main import ( "syscall/js" ) func countChars(this js.Value, args []js.Value) interface{} { println("[Go] countChars called") if len(args)

I was thinking "Can I run native binaries in the browser?" Naturally, my mind jumped to languages like Go or Rust—languages built compiling down to something close to the metal (bin).
But here's the twist: browsers don't speak Go or Rust.
They speak JavaScript. Or at least, they used to.
This is where WebAssembly comes in.
So... What is WASM?
WebAssembly (or WASM) is like a virtual CPU that runs inside your browser.
It defines its own instruction set.
It doesn't compile to x86 or ARM, but to its own binary format that the browser can understand and execute efficiently.
Think of it like this:
- The browser downloads a
.wasm
file. - That
.wasm
file contains instructions in a binary format. - Those instructions describe how to operate on a stack—push, pop, add, branch, loop, etc.
- The browser then either interprets or compiles those instructions into real machine code.
It’s kind of like Java’s JVM bytecode—but made for performance and for modern compilers. And unlike Java, it wasn’t designed in the 90s.
But Why Should I Care?
WebAssembly is fast. Like, really fast. Not "a little faster than JS"—but close-to-native fast.
That means you can take a performance-critical app (say, image processing, or running emulators) and run it in the browser without killing the fan.
The people behind WASM knew what they were doing.
The spec was designed by folks who deeply understand compiler and CPU internals.
So even though it’s portable and sandboxed, it can still get compiled to native CPU instructions quickly and efficiently.
Wait, Is It Like Assembly?
WASM borrows a lot of ideas from traditional assembly:
- It’s imperative: instructions run one after another.
- It has a stack: push values, pop them off, compute.
- It has branching, memory access, arithmetic—all the usual suspects.
But it’s not tied to any real CPU.
It’s not x86, not ARM, not RISC-V. It’s WASM’s own thing.
That’s why you can compile languages like Rust, C/C++, or Go into WASM.
These compilers already know how to emit instructions for different architectures—and WASM is just one more "target" for them.
Is It Only for Compiling Native Code?
Mostly, yes. That’s the main use case right now: compiling existing codebases written in C/C++/Rust/Go into something that runs in the browser.
But there's also WASI (WebAssembly System Interface), which is trying to bring system-level APIs to WASM—so you can run WASM outside the browser, too.
That’s where things get interesting.
Now you can run sandboxed WASM modules on the server, inside a CLI tool, or as plugins.
Think Docker containers, but even more lightweight.
What Can You Build With It?
Here’s where it gets fun. WebAssembly opens up a whole new set of use cases for the web (and beyond):
- Audio/video processing: things like FFmpeg or Audacity in the browser
- Games & emulators: NES, SNES, PS1 emulators running natively in JS environments
- Secure plugins: apps that allow user-contributed code (think OBS or VS Code extensions)
- Cross-platform apps: compile once, run everywhere—desktop, mobile, browser
And if you're doing backend stuff, some platforms (like Wasmer or Wasmtime) let you run WASM on the server.
That means you can build multi-tenant systems where user code runs isolated and safely.
Step-by-Step Setup
1. Project Structure
go-wasm
├─ Makefile
├─ client.html
├─ go.mod
├─ main.go
├─ main.wasm
└─ wasm_exec.js
2. main.go
: Export Go Function to JavaScript
package main
import (
"syscall/js"
)
func countChars(this js.Value, args []js.Value) interface{} {
println("[Go] countChars called")
if len(args) < 1 {
println("[Go] No argument provided")
return 0
}
input := args[0].String()
result := len(input)
println("[Go] Input received:", input)
println("[Go] Character count:", result)
return result
}
func main() {
println("[Go] WASM module initializing...")
js.Global().Set("countChars", js.FuncOf(countChars))
println("[Go] countChars function exported to JS")
select {} // keep it alive
}
3. Makefile
: Build and Init
build:
GOOS=js GOARCH=wasm go build -o main.wasm
init:
cp $$(find /usr/local/go -name wasm_exec.js | head -n 1) ./wasm_exec.js
-
make build
: Compiles the Go code tomain.wasm
. -
make init
: Copieswasm_exec.js
from Go's installation path (required to run WASM in browser).
And boom—your Go binary is now browser-ready.
4. client.html
: Browser UI + JS Glue
charset="utf-8" />
Go WASM Character Counter
Go WASM Character Counter
type="text" id="inputText" placeholder="Type something..." />
id="result">
const go = new Go(); // defined in wasm_exec.js
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject)
.then((result) => {
go.run(result.instance);
document.getElementById("countBtn").onclick = function () {
const input = document.getElementById("inputText").value;
const count = window.countChars(input);
document.getElementById("result").textContent = `Character count: ${count}`;
};
});
5. Serve It
You need to serve over HTTP (not file://
) because WASM loading via fetch()
needs a web server.
Run a local server:
python3 -m http.server 8080
# or
npx serve .
Open http://localhost:8080/client.html
in your browser.
## Conclusion
To tie it back—yes, you can compile Go code to WebAssembly.
The Go compiler has built-in support for GOARCH=wasm GOOS=js
.
You just write Go like normal, export a function, and run it in the browser with a little JavaScript glue.
So yeah—binaries in the browser? Turns out, it’s not just possible. It’s actually kind of awesome.
I’ve been actively working on a super-convenient tool called LiveAPI.
LiveAPI helps you get all your backend APIs documented in a few minutes
With LiveAPI, you can quickly generate interactive API documentation that allows users to execute APIs directly from the browser.
If you’re tired of manually creating docs for your APIs, this tool might just make your life easier.