3.4 Adding, Upgrading, and Removing Dependencies
Right, let’s talk about dependency management. This is where most Go developers, at some point, have quietly muttered “oh, come on” at their terminal. It’s not that go mod is bad—it’s actually brilliantly simple once you get it—it’s just that the world of dependencies is a messy, human place. My job is to make you the one who navigates it with confidence, not the one whose go.mod file looks like it survived a hurricane.
Think of your go.mod file as the single source of truth for your project’s identity and its needs. It’s not just a list of libraries; it’s the manifest for your entire build. The magic happens when you just… write Go code. See an import statement for a package you don’t have yet? Just go run your code, or go build it. The Go toolchain will see that unresolved import, politely go fetch the latest version of that module for you, and add it to your go.mod and its companion, the go.sum file, which locks down the cryptographic hashes to prevent “oops, a malicious actor uploaded a new version” situations.
But we’re not savages. We don’t just hope the toolchain reads our minds. We take control.
Adding a Dependency
The explicit command is go get. Want to add the glorious Cobra CLI library? You’d run:
go get github.com/spf13/cobra@latest
That @latest is important. It tells Go you want the, well, latest version. You could be more specific (@v1.7.0), or even request a potentially unstable future version (@v2.0.0-beta.1). The key thing to understand is that go get doesn’t just download the code; it resolves the dependency graph, updates go.mod, and downloads all the transitive dependencies (the libraries that your library needs) as well. It’s a full-dependency-graph operation, which is why it sometimes feels like it’s thinking reaaaally hard.
Upgrading with Purpose
This is where the real fun begins. Running go get -u will upgrade all the dependencies for the current module to their latest minor or patch releases. It’s like telling Go, “I trust you, give me all the non-breaking changes.”
go get -u ./...
The ./... means “everything in and below the current directory.” This is your standard-issue “update all my deps” command. But be warned: this is a fantastic way to accidentally break your code when a transitive dependency introduces a subtle change. You should always run your full test suite after a blanket upgrade. Every. Single. Time.
For more surgical strikes, use go list to see what’s available:
go list -m -versions github.com/spf13/cobra
This will show you all the tagged versions of Cobra. Then you can upgrade just that one dependency to a specific version:
go get github.com/spf13/cobra@v1.8.0
Why does this work so seamlessly? Because Go modules use semantic import versioning. The version is literally part of the import path for major versions v2 and above (e.g., github.com/foo/bar/v2). It’s a design choice that is initially confusing but ultimately genius for avoiding the “dependency hell” other ecosystems face.
Removing the Cruft
You go get a library, you write some code, and then you realize it’s terrible and you remove all the imports. You’d think Go would be smart enough to tidy up for you, right? Well, it is, but you have to ask nicely.
The go mod tidy command is your best friend. It’s the vacuum cleaner for your go.mod. It scans your source code, adds any dependencies that are missing from go.mod but are imported, and—crucially—removes any dependencies that are no longer needed.
go mod tidy
Never, ever check in your go.mod and go.sum files without running go mod tidy first. It’s like leaving the house with your fly down. It just screams amateur hour. This command also prunes away unused portions of the dependency graph, which is why your vendor directory might shrink after running it.
The go.mod File: A Quick Tour
You shouldn’t edit this file by hand often, but you must understand it. It’s not scary.
module github.com/yourname/yourproject // Your module's name. Its identity.
go 1.21 // The minimal Go version this module claims to work with.
require (
github.com/spf13/cobra v1.7.0 // direct dependency
github.com/inconshreveable/mousetrap v1.1.0 // indirect dependency
)
See that // indirect comment? That’s Go telling you, “Hey, you don’t import this directly; one of your direct dependencies does.” go mod tidy manages these comments for you. The designers made a questionable choice here by putting this metadata in a comment instead of a separate directive, but hey, it works, and we’ve all learned to live with it. The important part is knowing that your direct dependencies are the ones you asked for, and the rest are just their plus-ones.