A Go library that provides seamless integration with NodeJS, allowing Go programs to run JavaScript code through managed NodeJS instances.
- NodeJS Factory: Automatic detection and initialization of NodeJS
- Process Management: Start, monitor, and gracefully terminate NodeJS processes
- Process Pooling: Efficiently manage multiple NodeJS instances with auto-scaling
- JavaScript Execution: Run JS code with precise control and error handling
- IPC Communication: Bidirectional communication between Go and JavaScript
- Health Monitoring: Built-in process health checks and responsiveness verification
- Isolated Contexts: Create isolated JavaScript execution contexts within a process
- HTTP Integration: Serve HTTP requests directly to JavaScript handlers
go get github.com/KarpelesLab/nodejsThe factory is responsible for finding and initializing NodeJS:
factory, err:=nodejs.New()
iferr!=nil {
// handle error: nodejs could not be found or didn't run
}Create and manage individual NodeJS processes:
// Create a new process with default timeout (5 seconds)process, err:=factory.New()
iferr!=nil {
// handle error
}
deferprocess.Close()
// Run JavaScript without waiting for resultprocess.Run("console.log('Hello from NodeJS')", nil)
// Evaluate JavaScript and get resultctx, cancel:=context.WithTimeout(context.Background(), 5*time.Second)
defercancel()
result, err:=process.Eval(ctx, "2 + 2", nil)
iferr!=nil {
// handle error
}
fmt.Println("Result:", result) // Output: Result: 4For applications that need to execute JavaScript frequently, using a pool improves performance:
// Create a pool with auto-sized queue (defaults to NumCPU)pool:=factory.NewPool(0, 0)
// Get a process from the pool (waits if none available)ctx, cancel:=context.WithTimeout(context.Background(), 5*time.Second)
defercancel()
process, err:=pool.Take(ctx)
iferr!=nil {
// handle error: timeout or context cancelled
}
deferprocess.Close() // Returns process to pool// Execute JavaScript using the pooled processresult, err:=process.Eval(ctx, "(() => { return 'Hello from pooled NodeJS'; })()", nil)
iferr!=nil {
// handle error
}
fmt.Println(result)Register Go functions that can be called from JavaScript:
process.SetIPC("greet", func(paramsmap[string]any) (any, error) {
name, _:=params["name"].(string)
ifname=="" {
name="Guest"
}
returnmap[string]any{
"message": fmt.Sprintf("Hello, %s!", name),
}, nil
})
// In JavaScript, call the Go function:process.Run(` async function testIPC() { const result = await ipc('greet', { name: 'World' }); console.log(result.message); // Outputs: Hello, World! } testIPC();`, nil)JavaScript contexts provide isolated execution environments within a single NodeJS process:
// Create a NodeJS processproc, err:=factory.New()
iferr!=nil {
// handle error
}
deferproc.Close()
// Create an isolated JavaScript contextjsCtx, err:=proc.NewContext()
iferr!=nil {
// handle error
}
deferjsCtx.Close()
// Execute JavaScript in the contextctx:=context.Background()
result, err:=jsCtx.Eval(ctx, "var counter = 1; counter++;", nil)
iferr!=nil {
// handle error
}
fmt.Println("Result:", result) // Output: Result: 2// Create a second isolated contextjsCtx2, err:=proc.NewContext()
iferr!=nil {
// handle error
}
deferjsCtx2.Close()
// The second context has its own independent environmentresult2, err:=jsCtx2.Eval(ctx, "typeof counter", nil)
iferr!=nil {
// handle error
}
fmt.Println("Result2:", result2) // Output: Result2: undefinedYou can serve HTTP requests directly to JavaScript handlers using two approaches:
Method 1: Using Context ServeHTTPToHandler
// Create a JavaScript contextjsCtx, err:=proc.NewContext()
iferr!=nil {
// handle error
}
deferjsCtx.Close()
// Define a JavaScript HTTP handler_, err=jsCtx.Eval(context.Background(), `// Define a handler functionthis.apiHandler = function(request) { // Create response body const body = JSON.stringify({ message: "Hello, World!", method: request.method, path: request.url }); // Return a Response object (Fetch API compatible) return new Response(body, { status: 200, headers: { "Content-Type": "application/json" } });};`, nil)
// Create an HTTP handler that delegates to the JavaScript contexthttp.HandleFunc("/api", func(w http.ResponseWriter, r*http.Request) {
jsCtx.ServeHTTPToHandler("apiHandler", w, r)
})
// Start the HTTP serverhttp.ListenAndServe(":8080", nil)Method 2: Using Process ServeHTTPWithOptions
This approach allows specifying the context separately for better separation of concerns:
// Create a JavaScript context for API handlersapiContext, err:=proc.NewContext()
iferr!=nil {
// handle error
}
deferapiContext.Close()
// Define a handler in the context_, err=apiContext.Eval(context.Background(), `// API handlerthis.handler = function(request) { return new Response(JSON.stringify({ message: "Hello from API" }), { status: 200, headers: { "Content-Type": "application/json" } });};`, nil)
// Create an HTTP handler using ServeHTTPWithOptionshttp.HandleFunc("/api", func(w http.ResponseWriter, r*http.Request) {
// Specify options with the context IDoptions:= nodejs.HTTPHandlerOptions{
Context: apiContext.ID(),
}
// Call the handler directly with the context in optionsproc.ServeHTTPWithOptions("handler", options, w, r)
})
// Start the HTTP serverhttp.ListenAndServe(":8080", nil)// Create process with custom initialization timeoutprocess, err:=factory.NewWithTimeout(10*time.Second)
iferr!=nil {
// handle error
}// Verify process is responsiveiferr:=process.Checkpoint(2*time.Second); err!=nil {
// Process is not respondingprocess.Kill() // Force terminate if needed
}// Execute ES modulesprocess.Run(` import { createHash } from 'crypto'; const hash = createHash('sha256').update('hello').digest('hex'); console.log(hash);`, map[string]any{"filename": "example.mjs"})See the LICENSE file for details.