Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Go Module Management: Dependencies & Tidying

Recommended resources

Hello again! In our last lesson, we took the essential first step of initializing our simple-bank project as a Go module. You now have a go.mod file that declares your project's identity and Go version.

Today, we'll bring that file to life by adding external libraries. Managing these dependencies is a core skill for any project that goes beyond the standard library, and mastering it is crucial for building the complex systems you're aiming for.

Today's Learning Outcome: Manage direct and indirect dependencies using 'go get' and 'go mod tidy'.

We will cover how to add dependencies to your project, what happens when those dependencies have dependencies of their own, and how to keep your project's dependency list clean and accurate.

Adding Your First Dependency with go get

Our simple-bank project will eventually be a REST API. To handle HTTP requests, we'll need a router. While Go's standard library has a basic HTTP multiplexer, using a dedicated routing library offers more power and flexibility. We'll use chi, a popular and lightweight choice.

Adding an external library like chi makes it a direct dependencyβ€”a module that your own code explicitly imports and uses.

The primary command for adding or updating dependencies is go get. Let's use it to add chi to our project.

In your terminal, from the root of your simple-bank directory, run the following command:

go get github.com/go-chi/chi/v5

You'll see some output as Go downloads the module. After it finishes, two important files will have been modified:

  1. go.mod: A require directive for chi has been added. This line tells the Go toolchain that your module requires chi at a specific version (or a minimum version).
  2. go.sum: This file has been created or updated. It contains cryptographic hashes of your dependencies. We will explore this file in detail in our next lesson, but for now, know that it's Go's "lock file" for ensuring dependency integrity.

This video provides a clear, practical demonstration of the go get command in action.

Go Modules Explained | Mastering Dependency Management in Go

Watch this segment from 'Go Modules Explained' by Code & Learn. It shows exactly what happens when you run go get to add a new module and how you can then use it in your code.

About three minutes and twenty seconds into the video, watch the demonstration on adding direct dependencies. Pay attention to how the command updates the go.mod file and how you can specify a particular version of a dependency.

As the video mentions, you can also request a specific version of a module using the @ symbol. This is vital for maintaining stable builds. For example:

  • go get github.com/go-chi/chi/v5@v5.0.10: Gets a specific tagged version.
  • go get github.com/go-chi/chi/v5@latest: Gets the latest tagged version.

Direct vs. Indirect Dependencies and Version Selection

When you add a dependency like chi, you aren't just adding that one module. That module might have its own dependencies, which are known as indirect or transitive dependencies. Your project needs them to function, but you don't import them directly in your code.

Given your background, this is analogous to how package managers like Maven or npm handle transitive dependencies. However, Go's approach to resolving version conflicts is unique and powerful.

This short clip explains the concept of direct vs. indirect dependencies clearly.

Golang Modules, Versions, Dependency Management - Woohoo! πŸ˜ƒπŸŒ΄πŸ”†

This video from Learn To Code uses a simple 'puppy' and 'dog' example to illustrate the difference between a direct dependency (puppy) and an indirect one (dog).

About 30 seconds in, watch the dependency overview to solidify your understanding of this concept.

So, what happens if two of your dependencies require different versions of the same indirect dependency? For example, module A requires C v1.1.0 and module B requires C v1.2.0.

Go uses a simple and deterministic algorithm called Minimal Version Selection (MVS). Instead of trying to find the newest possible version of everything, Go selects the oldest compatible version of a dependency that satisfies the requirements of all other modules. In the example above, Go would select v1.2.0 because it's the minimum version that satisfies both A's and B's requirements.

This approach makes builds highly reproducible and avoids the "dependency hell" common in other ecosystems where installing a new package can unexpectedly upgrade dozens of others.

The article below and the accompanying diagram provide an excellent explanation of MVS.

GO EP11: Go Commands - go get, go mod tidy,... - Devtrovert

This blog post from Devtrovert, 'GO EP11: Go Commands', has a great section on how Go resolves dependencies. It explains Minimal Version Selection with a clear, practical example.

Find the subsection titled 'How Go Resolves Dependencies'. Read the MVS explanationβ€”it's a concise and accurate explanation of this core Go concept.

This diagram illustrates how MVS works. The 'main' module depends on A and B. A requires C v2.3.2, and B requires C v2.3.0. MVS resolves this by picking v2.3.2, the lowest version that satisfies both requirements.

Synchronizing Dependencies with go mod tidy

While go get is perfect for explicitly adding a dependency, there's another, more common workflow. Often, you'll first write the code, adding an import statement for a package you need. At this point, your go.mod file is out of sync with your code.

This is where go mod tidy comes in. It's a crucial command that acts as a "janitor" for your dependency files. It:

  1. Adds any missing modules that are required by import statements in your code.
  2. Removes any modules listed in go.mod that are no longer used by any of your code.

This ensures your go.mod file is a precise and minimal representation of your project's actual dependencies.

This next video segment demonstrates go mod tidy in both scenarios: adding a missing dependency and removing an unused one.

Go Modules Explained | Mastering Dependency Management in Go

Returning to 'Go Modules Explained', this section clearly demonstrates the power of go mod tidy to keep your dependencies synchronized with your code.

Starting about seven and a half minutes in, watch go mod tidy. Notice how the command automatically finds and adds a module after it's imported in the code, and later removes it when the import is deleted.

Hands-On Practice with go mod tidy

Let's try this with our simple-bank project.

  1. Add an import: Open main.go and modify it to use the uuid package for generating unique IDs. Don't worry about go get.

    package main
    
    import (
    	"fmt"
    	"github.com/google/uuid"
    )
    
    func main() {
    	id := uuid.New()
    	fmt.Println("Starting the simple bank API...")
    	fmt.Printf("Generated a new UUID: %s\n", id)
    }
    

    Your editor might flag the uuid import as an error because the module hasn't been downloaded yet.

  2. Run tidy: In your terminal, run:

    go mod tidy
    

    You'll see it "finding" and "downloading" the github.com/google/uuid module. Now, check your go.mod file. A require directive for uuid has been added automatically.

  3. Remove the import: Now, revert main.go to its original state, removing the uuid import and usage.

    package main
    
    import "fmt"
    
    func main() {
    	fmt.Println("Starting the simple bank API...")
    }
    
  4. Run tidy again:

    go mod tidy
    

    Check your go.mod file one more time. The require directive for github.com/google/uuid is now gone.

This cycleβ€”code, tidy, commitβ€”is a standard workflow in Go development. For a definitive written reference on these commands, the official Go documentation is invaluable.

Managing dependencies

The official Go documentation on 'Managing dependencies' provides the canonical explanation for these commands.

Briefly review the sections 'Adding a dependency' (where you can read about adding dependencies via go get) and 'Synchronizing your code’s dependencies' (which explains synchronizing dependencies via go mod tidy). This will reinforce what we've just practiced.


Conclusion

You've now mastered the essential commands for managing your project's dependencies. You can confidently add new libraries, understand how Go resolves version conflicts, and ensure your go.mod file stays clean and accurate.

Here are the key takeaways:

  • Use go get <module-path> to explicitly add a new dependency or update an existing one to a specific version.
  • An indirect dependency is a module required by one of your direct dependencies.
  • Go uses Minimal Version Selection (MVS) to deterministically and reproducibly select the oldest compatible versions of dependencies.
  • go mod tidy is your primary tool for synchronizing the go.mod file with the import statements in your source code.

Next Up

In this lesson, you saw that go get and go mod tidy both create and modify a file named go.sum. In our next lesson, we will focus entirely on this file. You will learn what it contains, why it's critical for security, and how it enables Go to create reproducible builds that you can trust.