Skip to main content







How to Supercharge Web Apps with Go and WebAssembly

How to Supercharge Web Apps with Go and WebAssembly

By Ege Aytin

You might’ve noticed the increasing chatter around WebAssembly (WASM) in the developer community. Its potential is vast, and at Permify, we’ve found it invaluable in enhancing our open source project. In this post, we’ll explore how integrating WebAssembly with Go elevated our web application’s performance and user experience.

I’m part of the team behind Permify — an open-source infrastructure that helps developers create and manage granular permissions throughout their applications. One of our key features is an interactive Playground used for building and testing authorization models. Our decision to integrate WebAssembly into the Permify Playground brought substantial benefits, which we’ll dive into here.

Why Use WebAssembly with Go?

WebAssembly (Wasm) enables fast, efficient code execution in web browsers. It acts as a bridge between high-level languages and near-native performance levels in web apps. Here are the core advantages:

  • Universal browser support
  • Close-to-native execution speed

Go’s static typing and concurrency strengths make it a solid backend choice, and WebAssembly allowed us to bring these strengths directly into the browser without depending on server-side processing. The result? A highly responsive, maintainable, and interactive Playground experience for our users.

The Benefits of WASM in the Permify Playground

Our key goals for the Playground experience included simplicity, high performance, and direct feedback from users. With WASM, we achieved:

  • Zero server-side processing
  • Instant, in-browser computation
  • Better scalability and portability
  • Positive developer feedback and engagement

Getting Started with Go and WebAssembly

Here’s how we implemented WebAssembly in the Playground using Go:

1. Compile Go to WebAssembly

Set the target platform:

GOOS=js GOARCH=wasm go build -o main.wasm main.go

Optimize the binary:

wasm-opt main.wasm --enable-bulk-memory -Oz -o play.wasm

2. Handle Events from JavaScript

Example Go code to register a button click:

import "syscall/js"

func registerCallbacks() {
  js.Global().Set("handleClick", js.FuncOf(handleClick))
}

func handleClick(this js.Value, inputs []js.Value) interface{} {
  println("Button clicked!")
  return nil
}

Corresponding HTML:

<button onclick="window.handleClick()">Click me</button>

3. Initialize the WebAssembly Module

<script src="wasm_exec.js"></script>
<script>
  const go = new Go();
  WebAssembly.instantiateStreaming(fetch("play.wasm"), go.importObject).then((result) => {
    go.run(result.instance);
  });
</script>

4. DOM Interaction from Go

func updateDOMContent() {
  document := js.Global().Get("document")
  element := document.Call("getElementById", "myParagraph")
  element.Set("innerText", "Updated content from Go!")
}

5. Use Goroutines in the Browser

func fetchData(url string, ch chan string) {
  ch <- "Data from " + url
}

func main() {
  ch := make(chan string)
  go fetchData("https://api.example1.com", ch)
  go fetchData("https://api.example2.com", ch)

  data1 := <-ch
  data2 := <-ch
  println(data1, data2)
}

Permify’s WASM Code Structure

1. WASM-Specific Build Tags

//go:build wasm
// +build wasm

Ensures this code is compiled only for WebAssembly targets.

2. Running JS-to-Go Functions

A function run marshals JSON from JavaScript, processes it in Go, then returns a response JSON string, enabling two-way data exchange between JavaScript and Go.

3. Main Execution

func main() {
  ch := make(chan struct{}, 0)
  dev = development.NewContainer()
  js.Global().Set("run", run())
  <-ch
}

This keeps the WebAssembly module alive so it can be interacted with via JavaScript.

Embedding WASM in a React Application

1. Directory Structure

loadWasm/
├── index.tsx
├── wasm_exec.js
└── wasmTypes.d.ts

2. TypeScript Declarations

declare global {
  export interface Window {
    Go: any;
    run: (shape: string) => any[];
  }
}
export {};

3. Load WASM in React Component

async function loadWasm(): Promise<void> {
  const goWasm = new window.Go();
  const result = await WebAssembly.instantiateStreaming(fetch("play.wasm"), goWasm.importObject);
  goWasm.run(result.instance);
}

Called within a React component’s useEffect to load WASM when the component mounts.

4. Using the run Function

function Run(shape) {
  return new Promise((resolve) => {
    resolve(window.run(shape));
  });
}

5. Button Component to Execute WASM

function RunButton({ shape, onResult }) {
  const handleClick = async () => {
    let result = await Run(shape);
    onResult(result);
  };

  return <button onClick={handleClick}>Run WebAssembly</button>;
}

6. Main App Integration

const shapeContent = {
  schema: "...",
  relationships: [...],
  attributes: [...],
  scenarios: [...]
};

function App() {
  const [result, setResult] = useState([]);

  return (
    <div>
      <RunButton shape={JSON.stringify(shapeContent)} onResult={setResult} />
      <ul>
        {result.map((item, i) => <li key={i}>{item}</li>)}
      </ul>
    </div>
  );
}

Conclusion

We explored the integration of Go and WebAssembly, providing both a high-level overview and a technical walkthrough. By combining the performance of Go with the efficiency of WebAssembly, we brought powerful server-side functionality into the browser. This approach has simplified our development process, improved responsiveness, and provided a flexible platform for modeling permissions with Permify’s Playground.

If you’re considering building web applications that require both performance and rich interactivity, leveraging Go with WebAssembly is a compelling choice.


Leave a Reply

Close Menu

Wow look at this!

This is an optional, highly
customizable off canvas area.

About Salient

The Castle
Unit 345
2500 Castle Dr
Manhattan, NY

T: +216 (0)40 3629 4753
E: hello@themenectar.com