Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions contents/2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
---
title: How to distribute Go binaries with NPM
publishing_date: 2025-09-27
author: Clelia Astra Bertelli
---

[NPM](https://npmjs.org) 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`](https://github.com/Nelwhix/go-npm).

To do so, we'll follow these steps:

- create a very simple ‘hello world’ program in Go
- create a `.goreleaser.yaml` file to configure [GoReleaser](https://goreleaser.com) so that we can easily carry out cross-platform builds
- create a `package.json` to package and distribute the binaries
- create a `.github/workflows/release.yaml` file to automate the release whenever we want to publish the package

Without further ado, let's dive in!

## 1. Create a Hello World in Go

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`:

```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:

```bash
go build . -o hello-world
./hello-world
```

And you will get a nice `Hello world` printed out on the console.

## 2. Create a .goreleaser.yaml

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:

```yaml
builds:
- binary: hello-world
goos:
- windows
- darwin
- linux
goarch:
- amd64
- arm64
```

As you can see, we specify:

- The name of the binary (`binary`)
- The Operative Systems for which the binary will be built (`goos` , which in our case is an array of the most common OS, Windows, Dawin/macOS and Linux)
- The processor architecture for the target operating system (`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.

## 3. Create a package.json

`package.json` is the file that is used to specify package information for NPM packages. Specifically, in our `package.json` we will specify:

- Name of the package and several other metadata useful for discovery on the NPM registry (like the source repository, the name of the author, keywords, where to submit issues…)
- Dependencies, among which we will specify `go-npm`, which is crucial for building our package locally, once it has been pulled from NPM registry
- A post-install script, which will install the Go binary on our system
- The target Go binary to install from our GitHub repository (binary that we built and published with GoReleaser)

Here is an example of the `package.json` file for our hello world project:

```json
{
"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"
}
}

```

## 4. Automate with a GitHub Action

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`:

```yaml
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:

- Install Node and use `npm` to install GoReleaser globally
- Set up NPM authentication (for which you will need an NPM access token configured under `NPM_TOKEN` in your GitHub repository secrets)
- Release the package to npm via `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)

## 5. Test the Release Pipeline

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:

```bash
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:

```bash
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!
38 changes: 21 additions & 17 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,34 @@ go 1.24.5

require (
github.com/a-h/templ v0.3.943
github.com/blevesearch/bleve v1.0.14
github.com/blevesearch/bleve/v2 v2.5.3
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
)

require (
github.com/RoaringBitmap/roaring v0.4.23 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.8 // indirect
github.com/blevesearch/geo v0.2.4 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/mmap-go v1.0.2 // indirect
github.com/blevesearch/segment v0.9.0 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/zap/v11 v11.0.14 // indirect
github.com/blevesearch/zap/v12 v12.0.14 // indirect
github.com/blevesearch/zap/v13 v13.0.6 // indirect
github.com/blevesearch/zap/v14 v14.0.5 // indirect
github.com/blevesearch/zap/v15 v15.0.3 // indirect
github.com/couchbase/vellum v1.0.2 // indirect
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/golang/protobuf v1.3.2 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/json-iterator/go v0.0.0-20171115153421-f7279a603ede // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/philhofer/fwd v1.0.0 // indirect
github.com/steveyen/gtreap v0.1.0 // indirect
github.com/tinylib/msgp v1.1.0 // indirect
github.com/willf/bitset v1.1.10 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
golang.org/x/sys v0.34.0 // indirect
)
Loading
Loading