Leveraging Golang in NodeJS Applications: Part 1 (NAPI)
Ask Google about Go, and you’d likely see, “It’s a simple, reliable language built for portability and efficient concurrency.” If you ask me, I’ll tell you that most distinctive characteristic of Go is the develoment velocty which is, in most cases, as good as velocity on JS or Python projects.
Working on NodeJS projects for the past 8 years I can tell that in Golang you can code some things much faster than in NodeJS. In fact some things could be better done with Go for the sake of performance and memory efficiency. NodeJS on the other hand has one huge advantage - ecosystem. And the army of developers are included in the term “ecosystem”. On that note, I want to say I would always choose to use Golang along with NodeJS leveraging both ecosystems to my advantage, so the argument “Golang vs NodeJS” doesn’t make sense to me.
I want to share some tips on how to leverage Golang in NodeJS projects. This can be helpful if you need to cut down on compute costs or use go libraries in your NodeJS application.
NodeJS NAPI
NAPI is an interface for building native Addons for NodeJS in C++. In this example I will use node-addon-api which provides C++ wrappers for V8 types. It will allow us to glue Go libraries with NodeJS.
Short example:
// src/binding.cc
#include <napi.h>
Napi::String Method(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, "world");
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "hello"),
Napi::Function::New(env, Method));
return exports;
}
NODE_API_MODULE(hello, Init)
Using in javascript:
//src/binding.js
var addon = require('bindings')('hello');
console.log(addon.hello());
Golang’s CGO
On the official Go documentation it is said:
Cgo enables the creation of Go packages that call C code.
CGO also enables us to call go functions from other languages. And even use Go in C/C++ projects if needed.
It is achieved using directive //export
, and by using types provided by the C
package.
Example:
// hello/main.go
package main
// #include <stdlib.h>
// #include <stdio.h>
import "C"
import "fmt"
//export Say
func Say(what string) {
fmt.Println(what)
}
Now we can build it a shared library to use it with any language:
go build -o hello.so -buildmode=c-shared
Or better, we can compile it as an bin archive and use it with other compilers:
go build -o hello.a -buildmode=c-archive
When using CGO the compiler also produces a header file which we can use in our C++ source hello.h
.
NodeJS Addon Compilation
NodeJS provides node-gyp - a tool for the addon compilation.
node-gyp usually does not require complex configuration. All you need is to include source files and libraries in binding.gyp
file.
//binding.gyp
{
"targets": [
{
"target_name": "main",
"sources": [
"src/hello.h",
"src/binding.cc"
],
"libraries": [ "hello/hello.a" ],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
}
]
}
Calling Go function from C++
In order to call a Go function include the header file hello.h"
so you can use function Say()
and Golang types.
// src/binding.cc
#include <napi.h>
#include "hello.h"
static void Hello(const Napi::CallbackInfo& info) {
Napi::Buffer<uint8_t> buf = args[0].As<Napi::Buffer<uint8_t>>();
GoString gostr = {reinterpret_cast<char *>(buf.Data()), long(buf.Length())};
Say(gostr);
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("hello", Napi::Function::New(env, Hello));
return exports;
}
NODE_API_MODULE(hello, Init)
Javascript:
// src/binding.js
const binding = require('./binding.napi')
binding.Say(Buffer.from('Hello!'))
// > Hello!
Full example
If you’re interested to explore further, a full-featured example of using Golang with NodeJS can be found in this repo