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/nodejs
The factory is responsible for finding and initializing NodeJS:
factory, err := nodejs.New()
if err != 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()
if err != nil {
// handle error
}
defer process.Close()
// Run JavaScript without waiting for result
process.Run("console.log('Hello from NodeJS')", nil)
// Evaluate JavaScript and get result
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := process.Eval(ctx, "2 + 2", nil)
if err != nil {
// handle error
}
fmt.Println("Result:", result) // Output: Result: 4
For 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)
defer cancel()
process, err := pool.Take(ctx)
if err != nil {
// handle error: timeout or context cancelled
}
defer process.Close() // Returns process to pool
// Execute JavaScript using the pooled process
result, err := process.Eval(ctx, "(() => { return 'Hello from pooled NodeJS'; })()", nil)
if err != nil {
// handle error
}
fmt.Println(result)
Register Go functions that can be called from JavaScript:
process.SetIPC("greet", func(params map[string]any) (any, error) {
name, _ := params["name"].(string)
if name == "" {
name = "Guest"
}
return map[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 process
proc, err := factory.New()
if err != nil {
// handle error
}
defer proc.Close()
// Create an isolated JavaScript context
jsCtx, err := proc.NewContext()
if err != nil {
// handle error
}
defer jsCtx.Close()
// Execute JavaScript in the context
ctx := context.Background()
result, err := jsCtx.Eval(ctx, "var counter = 1; counter++;", nil)
if err != nil {
// handle error
}
fmt.Println("Result:", result) // Output: Result: 2
// Create a second isolated context
jsCtx2, err := proc.NewContext()
if err != nil {
// handle error
}
defer jsCtx2.Close()
// The second context has its own independent environment
result2, err := jsCtx2.Eval(ctx, "typeof counter", nil)
if err != nil {
// handle error
}
fmt.Println("Result2:", result2) // Output: Result2: undefined
You can serve HTTP requests directly to JavaScript handlers using two approaches:
Method 1: Using Context ServeHTTPToHandler
// Create a JavaScript context
jsCtx, err := proc.NewContext()
if err != nil {
// handle error
}
defer jsCtx.Close()
// Define a JavaScript HTTP handler
_, err = jsCtx.Eval(context.Background(), `
// Define a handler function
this.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 context
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
jsCtx.ServeHTTPToHandler("apiHandler", w, r)
})
// Start the HTTP server
http.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 handlers
apiContext, err := proc.NewContext()
if err != nil {
// handle error
}
defer apiContext.Close()
// Define a handler in the context
_, err = apiContext.Eval(context.Background(), `
// API handler
this.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 ServeHTTPWithOptions
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
// Specify options with the context ID
options := nodejs.HTTPHandlerOptions{
Context: apiContext.ID(),
}
// Call the handler directly with the context in options
proc.ServeHTTPWithOptions("handler", options, w, r)
})
// Start the HTTP server
http.ListenAndServe(":8080", nil)
// Create process with custom initialization timeout
process, err := factory.NewWithTimeout(10 * time.Second)
if err != nil {
// handle error
}
// Verify process is responsive
if err := process.Checkpoint(2 * time.Second); err != nil {
// Process is not responding
process.Kill() // Force terminate if needed
}
// Execute ES modules
process.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.