Unlocking Go's Power: How Goja Brings JavaScript to Your Go Apps

This article explores Goja, a pure‑Go JavaScript engine, detailing its features, integration with the K6 load‑testing tool, and practical code examples for embedding scripts, passing values, handling structs, invoking Go functions, error handling, and VM pooling to achieve high performance in Go applications.

FunTester
FunTester
FunTester
Unlocking Go's Power: How Goja Brings JavaScript to Your Go Apps

Embedded script engines give developers flexibility for dynamic extensions and cross‑language integration. Goja is a lightweight, pure‑Go JavaScript engine that enables Go applications to run JavaScript code without external C/C++ dependencies.

Goja Overview

Goja is a pure Go implementation of a JavaScript runtime, compatible with ECMAScript 5.1. It runs JavaScript inside Go programs, offering natural compatibility and cross‑platform support while avoiding the overhead of native engines like V8.

Key Features

Pure Go implementation : No extra bindings or C libraries, allowing seamless embedding in any Go project.

ECMAScript 5.1 compatibility : Supports the majority of common JavaScript use cases despite lacking ES6/ES7 features.

High performance and safety : Minimal runtime overhead and reduced security surface because it avoids external native code.

Lightweight : Small footprint makes it ideal for microservices and embedded scenarios.

Typical Use Cases

Dynamic extension in web applications : Execute custom JavaScript for page rendering or complex business logic.

Automation and scripting : Write scripts to automate workflows or implement configurable rule engines.

Plugin and extension systems : Provide a scriptable plugin layer for Go applications.

Goja in K6 Load Testing

K6, an open‑source load‑testing tool, uses Goja as its JavaScript execution engine. Test scripts are written in JavaScript, parsed and run by Goja within a Go process. Because Goja implements only ES5.1, K6 adopts a synchronous execution model, wrapping I/O operations to keep scripts predictable.

Example K6 script:

import http from 'k6/http';
import { check } from 'k6';

export default function () {
    const res = http.get('https://FunTester-api.com');
    check(res, { 'status was 200': (r) => r.status === 200 });
}

Basic JavaScript Execution in Go

Creating a Goja runtime and running a simple script that adds two numbers:

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()
    script := `
        function add(a, b) {
            return a + b;
        }
        add(10, 20);
    `
    result, err := vm.RunString(script)
    if err != nil {
        panic(err)
    }
    fmt.Println("Result:", result) // Output: 30
}

Passing Values Between Go and JavaScript

Example of passing a Go slice to JavaScript, filtering even numbers, and retrieving the result:

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()
    values := []int{}
    for i := 1; i <= 10; i++ {
        values = append(values, i)
    }
    script := `
        values.filter((x) => {
            return x % 2 === 0;
        })
    `
    vm.Set("values", values)
    result, err := vm.RunString(script)
    if err != nil {
        panic(err)
    }
    filteredValues := result.Export().([]interface{})
    fmt.Println(filteredValues) // [2 4 6 8 10]
}

Structs and Method Calls

Goja can expose Go structs to JavaScript, allowing field access and method invocation. The engine respects Go naming conventions and can map method names to camelCase via FieldNameMapper.

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

type Person struct {
    Name string
    age  int
}

func (p *Person) GetAge() int {
    return p.age
}

func main() {
    vm := goja.New()
    person := &Person{Name: "John Doe", age: 30}
    vm.Set("person", person)
    script := `
        const name = person.Name;    // exported field
        const age = person.GetAge(); // method call
        name + " is " + age + " years old.";
    `
    result, err := vm.RunString(script)
    if err != nil {
        panic(err)
    }
    fmt.Println(result.String()) // Output: John Doe is 30 years old.
}

Calling Go Functions from JavaScript

Go functions can be registered in the runtime and invoked directly from a script:

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()
    helloFunc := func(call goja.FunctionCall) goja.Value {
        name := call.Argument(0).String()
        result := fmt.Sprintf("Hello, %s!", name)
        return vm.ToValue(result)
    }
    vm.Set("hello", helloFunc)

    script := `
        hello("Goja from FunTester");
    `
    result, err := vm.RunString(script)
    if err != nil {
        panic(err)
    }
    fmt.Println(result) // Output: Hello, Goja from FunTester!
}

Error Handling

JavaScript exceptions are propagated as Go errors and can be handled using standard Go error checking:

package main

import (
    "fmt"
    "github.com/dop251/goja"
)

func main() {
    vm := goja.New()
    script := `
        function errorFunc() {
            throw new Error("Something went wrong!");
        }
        errorFunc();
    `
    _, err := vm.RunString(script)
    if err != nil {
        fmt.Println("Caught error:", err)
    }
}

VM Pooling for Performance

Creating a sync.Pool of Goja runtimes reduces the cost of repeatedly initializing VMs, which is especially beneficial for high‑throughput scenarios such as K6 load testing.

package main

import (
    "fmt"
    "sync"

    "github.com/dop251/goja"
)

var vmPool = sync.Pool{
    New: func() interface{} {
        vm := goja.New()
        vm.Set("add", func(a, b int) int { return a + b })
        return vm
    },
}

func main() {
    vm := vmPool.Get().(*goja.Runtime)
    defer vmPool.Put(vm)

    result, err := vm.RunString(`add(1, 2)`)
    if err != nil {
        panic(err)
    }
    fmt.Println(result) // Output: 3
}

Overall, Goja offers a flexible, lightweight, and high‑performance JavaScript runtime for Go developers, suitable for scripting, plugin systems, and performance‑critical tools like K6.

performanceJavaScriptGoEmbeddingscriptingK6Goja
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.