Zero Dependencies In Go

Tim Bray created a badge for projects that have zero dependencies. A noble cause!

https://www.tbray.org/ongoing/When/202x/2024/09/04/0dependencies

Go makes zero dependencies reasonably easy compared to other languages, but that’s not to say you shouldn’t use any. In my day-to-day work I rely on a handful of tried and tested dependencies for a lot of my project work:

  • Chi for routing, although I haven’t had the chance to assess the updates to the stdlib http package that came in Go 1.22 which may make its use unnecessary.
  • sqlx which extends the database/sql package and adds a lot of helpful functionality.
  • validator which provides a comprehensive validation library but is overkill if all you need are some simple checks.

It doesn’t make sense to write this functionality from scratch and maintain it when these packages have lots of community contributions, are actively maintained, and for libraries like Chi, have zero dependencies themselves. How Go is generally written means you’re unlikely to have the considerable dependency graphs of other languages. Even so, I’ve seen Go projects that have pretty significant go.sum files, often containing multiple versions of the same lib because the culture of the organisation was to import.

But that’s not to say zero dependency isn’t something to strive for. Every dependency removed or not installed in the first place, means fewer CVEs and, as Tim mentions in his post, more predictable performance.

Adding enforcement

One way to keep dependencies to a minimum is to enforce allowed dependencies using depguard, which is a linter available in golanglint-ci.

Here’s an example from a golanglint-ci config:

linters:
  disable-all: true
  enable:
    - depguard

linters-settings:
  depguard:
    rules:
      main:
        allow:
          - $gostd
          - github.com/tomjowitt/gotidal

The allow lines are self-explanatory. This project allows imports from two sources: The standard library, and the project module as defined in the go.mod file.

module github.com/tomjowitt/gotidal

go 1.22.0

With this approach, you can fail builds if new dependencies creep in. Having an allow list has two benefits:

  • It forces developers to think, “Do I need this?” before they add another exception.
  • What has been added is very explicit during code review.

You can even store your lint file outside the repo as read-only and pull it into CI at runtime, ensuring no exceptions can be added without authorisation. This works really well in heavily audited, compliance-heavy environments where dependencies are scrutinised before being added.

Earlier this year, I decided to try to write a library that used Go best practices, including zero dependencies. It was surprisingly easy. You can see the results, including the zero dependency badge, here:

https://github.com/tomjowitt/gotidal

All hail the standard library!