Write JavaScript.
Ship a Go binary.
Gun is a JavaScript-to-Go transpiler. It compiles your codebase and the npm dependencies underneath it into Go that runs on real hardware, with Node and Bun compatibility intact.
npm i -g gun-transpilerat 240+ companies
JavaScript in. Go out.
import { createServer } from 'http'
const port = 8080
const handler = (req, res) => {
const body = JSON.stringify({
message: 'hello world',
path: req.url,
ok: true,
})
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(body)
}
createServer(handler).listen(port)package main
import (
jsvalue "github.com/nnstd/gun/runtime/builtin"
json "github.com/nnstd/gun/runtime/builtin/json"
nodehttp "github.com/nnstd/gun/runtime/http"
eventloop "github.com/nnstd/gun/runtime/eventloop"
)
func main() {
handler := jsvalue.NewFunction(func(args ...*jsvalue.JSValue) *jsvalue.JSValue {
req := args[0]
res := args[1]
body := json.AsJSValue.Get("stringify").Call(jsvalue.ObjectFrom(map[string]any{
"message": jsvalue.NewString("hello world"),
"path": req.Get("url"),
"ok": jsvalue.NewBool(true),
}))
res.MethodCall("writeHead", jsvalue.NewNumber(200))
res.MethodCall("end", body)
return nil
})
nodehttp.AsJSValue.Get("createServer").Call(handler)
eventloop.Default.Run()
}Numbers that survive a flame graph.
Same workload, a JSON-emitting HTTP server with one DB query, compiled three ways. Results from repeated runs on Hetzner CCX23. Reproduce them yourself from the benchmark repo.
Five stages between .js and ./bin.
Gun is not a VM, not a wrapper, and not a bundler. It is a source-to-source compiler with a runtime designed so the emitted Go stays inspectable and operationally boring.
Parse
Tree-sitter parses CommonJS, ESM, JSX, and TypeScript with sub-millisecond incremental updates.
Resolve
Walks node_modules and tsconfig paths and resolves CJS or ESM interop the same way Node and Bun do.
Analyze
Type flow and scope analysis map JavaScript semantics to Go equivalents, static where possible and JSValue where needed.
Emit
Outputs Go using the JSValue runtime with gofmt-clean output and source maps back to the original files.
Compile
go build takes over and gives you a static binary ready for containers, bare metal, or a one-shot porting workflow.
Built for the parts that hurt.
npm dependencies, transpiled in place
Gun follows your import graph all the way down, compiling the packages below your app instead of trapping them in a JS runtime.
A real Go binary, not a JS sandbox
The output is plain Go using a small runtime. Vendor it, audit it, fork it, or treat Gun as a migration tool and stop running it after the first successful port.
Source maps you can trust
Stack traces resolve to the original .js or .ts line, so runtime failures stay debuggable after compilation.
Incremental, by default
Tree-sitter rebuilds only what changed. Watch mode stays in the tens of milliseconds on large codebases.
Open, MIT, audited
The transpiler and runtime are both MIT. Replace pieces, inspect the output, and keep control of your delivery path.
We replaced a Node fleet with Gun-compiled binaries over a weekend. P99 fell 4x, our memory bill fell 3x, and nobody had to learn Go.
Things engineers ask before they install.
Honest answers. If yours is not here, ask in Discord or open an issue and treat the generated Go as part of the contract.
Pure-JS packages usually transpile cleanly. Native bindings such as sharp or canvas need to be marked external and wired through cgo or a Go equivalent.
Pull the trigger.
Free, MIT-licensed, and two commands away from your existing package.json. The rewrite is the compiler, not your application.
npm i -g gun-transpiler