NPM is a foundational piece of modern software development, since it’s the registry (and package manager) that stores all the packages for JavaScript and Typescript world.
Despite clearly being oriented toward the JS/TS world, NPM can also be used to distribute binaries from other languages, like Go, Rust or C/C++.
In this article, I would like to walk you through how you can easily release a Go package and distribute it through npm, thanks to an extremely useful utility called go-npm
.
To do so, we’ll follow these steps:
.goreleaser.yaml
file to configure GoReleaser so that we can easily carry out cross-platform buildspackage.json
to package and distribute the binaries.github/workflows/release.yaml
file to automate the release whenever we want to publish the packageWithout further ado, let’s dive in!
If you’re interested in this tutorial, you probably already know Go, but, assuming you don’t, let’s create a very simple ‘hello world’ script that we will save under main.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello world!")
}
In order to build and run this Go script, you simply need to run:
go build . -o hello-world
./hello-world
And you will get a nice Hello world
printed out on the console.
GoReleaser is an extremely useful utility to publish Go binaries on platforms such as GitHub.
In order to run it, you simply need to add a .goreleaser.yaml
file in the root directory of the Go program you want to build.
Here is an example for our hello world:
builds:
- binary: hello-world
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm64
As you can see, we specify:
binary
)goos
, which in our case is an array of the most common OS, Windows, Dawin/macOS and Linux)goarch
, which in our case is an array with the two major available architectures, AMD64 and ARM64)Once this is configured, you will be able to automate the builds of your binary effortlessly.
package.json
is the file that is used to specify package information for NPM packages. Specifically, in our package.json
we will specify:
go-npm
, which is crucial for building our package locally, once it has been pulled from NPM registryHere is an example of the package.json
file for our hello world project:
{
"name": "@namespace/hello-world", // change @namespace to your actual namespace!
"version": "0.1.0",
"description": "My first Go project published on NPM!",
"main": "index.js",
"scripts": {
"postinstall": "golang-npm install",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/your-name/hello-world.git" // change your-name to your actual GitHub username!
},
"keywords": [
"hello world",
"go",
"npm",
"tutorial"
],
"author": "Your Name",
"license": "MIT",
"bugs": {
"url": "https://github.com/your-name/hello-world/issues"
},
"homepage": "https://github.com/Ayour-name/hello-world#readme",
"dependencies": {
"golang-npm": "^0.0.6"
},
"goBinary": {
"name": "hello-world",
"path": "./bin",
"url": "https://github.com/your-name/hello-world/releases/download/v{{version}}/git-push-blog_{{version}}_{{platform}}_{{arch}}.tar.gz"
}
}
Once we have everything in place, we can automate the publishing with a GitHub Action that runs every time we push a tag to it.
Here is the code, that you should save under .github/workflows/release.yml
:
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: read # for checkout
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Install goreleaser
run: npm i -g @goreleaser/goreleaser
- name: Release Go binary
run: goreleaser release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup npm authentication
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Release NPM package
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public
As you can see, in this action we:
npm
to install GoReleaser globallyNPM_TOKEN
in your GitHub repository secrets)npm publish
(it is important to specify --access public
if you do not have a paying account and thus do not have access to private packages)Once you are ready, create a GitHub repository under your-name/hello-world
and push all the code you just wrote to the main branch.
Once that is set, you can run:
git tag v0.1.0
git push origin v0.1.0
This way, GitHub will kick off the release action and, in a couple of minutes, you will be able to see the release on GitHub and the package on NPM.
You can then test it by installing it with:
npm install @namespace/hello-world
hello-world # the binary should be available globally
And you’re done: you’ve successfully built, pushed and installed your first Go binary with NPM!