Backend Development 15 min read

Understanding Go Modules: Evolution, Usage, and Best Practices

The article traces Go’s shift from GOPATH to Go Modules—detailing their history, migration steps, practical commands, and best‑practice tips such as using replace directives, managing go.mod and go.sum, and emphasizing why teams should adopt modules now for reliable dependency handling, security, and developer efficiency.

Tencent Cloud Developer
Tencent Cloud Developer
Tencent Cloud Developer
Understanding Go Modules: Evolution, Usage, and Best Practices

According to a 2020 internal Tencent developer report, Go has become the second most used backend language within the company. The rapid adoption of Go across many flagship products (Tencent Cloud, WeChat, Tencent Video, etc.) exposed several challenges such as internal certificate signing and security auditing. The introduction of Go Modules has gradually addressed many of these issues.

The author, responsible for Tencent Cloud's Go usage and the internal goproxy service since early 2021, shares a comprehensive series on Go Modules, covering its history, migration from GOPATH, and practical usage.

1. Evolution of Golang Development Modes

Initially, Go developers relied on the GOPATH environment variable. As Go grew, dependency problems emerged, leading to the development of Go Modules.

2. Using GOPATH

GOPATH mode provides several commands:

go install installs binaries to $GOBIN (default $GOPATH/bin ).

go install also places compiled packages under $GOPATH/pkg .

go get downloads source code to $GOPATH/src .

3. Development History of Go Modules

Go 1.11 (Aug 2018) introduced the GO111MODULE environment variable with default auto . When auto is set, Go decides the mode based on the presence of a go.mod file outside $GOPATH/src .

Go 1.13 (Aug 2019) adjusted the auto behavior to enable module mode even if a go.mod exists inside $GOPATH/src .

Go 1.16 (Feb 2021) changed the default to GO111MODULE=on , effectively disabling GOPATH mode unless explicitly overridden.

Future Go versions plan to remove the GO111MODULE variable and fully deprecate GOPATH mode.

4. Interaction Between GOPATH and Go Modules

Even after GOPATH mode is deprecated, the GOPATH variable remains for:

Binary installation path ( $GOPATH/bin ).

Module cache ( $GOPATH/pkg/mod ).

Checksum database ( $GOPATH/pkg/sumdb ).

Developers can still place code under $GOPATH/src/import/path and add a go.mod file to enable module mode.

5. Frequently Asked Questions

(1) Will GOPATH be removed? No, it will stay for the reasons above.

(2) Can I continue to create repositories under GOPATH/src ? Yes, just add a go.mod file.

(3) How to test modifications of a dependency? Use the replace directive in go.mod .

6. Getting Started with Go Modules

Step 1: Create a new module.

package hello

func Hello() string {
    return "Hello, world."
}

Create a test file.

package hello

import "testing"

func TestHello(t *testing.T) {
    want := "Hello, world."
    if got := Hello(); got != want {
        t.Errorf("Hello() = %q, want %q", got, want)
    }
}

Running go test fails because no go.mod file exists:

$ go test
go.mod file not found in current directory or any parent directory; see 'go help modules'

Initialize the module:

$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
go: to add module requirements and sums:
    go mod tidy
$ go mod tidy
$ go test
PASS
ok example.com/hello  0.020s

The generated go.mod file:

module example.com/hello

go 1.17

Step 2: Add a dependency (e.g., rsc.io/quote ).

package hello

import "rsc.io/quote"

func Hello() string {
    return quote.Hello()
}

Running tests prompts to fetch the dependency:

$ go test
hello.go:3:8: no required module provides package rsc.io/quote; to add it:
    go get rsc.io/quote
$ go get rsc.io/quote
go: downloading rsc.io/quote v1.5.2
... (other downloads)
$ go test
PASS
ok example.com/hello  1.401s

Inspect the module graph:

$ go list -m all
example.com/hello
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
rsc.io/quote v1.5.2
rsc.io/sampler v1.3.0

The go.mod now contains indirect dependencies:

module example.com/hello

go 1.17

require (
    golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
    rsc.io/quote v1.5.2 // indirect
    rsc.io/sampler v1.3.0 // indirect
)

Step 3: Update dependencies, e.g., upgrade golang.org/x/text :

$ go get golang.org/x/text
go: downloading golang.org/x/text v0.3.7
go: upgraded golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c => v0.3.7
$ go test
PASS
ok example.com/hello  0.013s

After updating, the go.mod reflects the new version.

module example.com/hello

go 1.17

require (
    golang.org/x/text v0.3.7 // indirect
    rsc.io/quote v1.5.2 // indirect
    rsc.io/sampler v1.3.0 // indirect
)

Step 4: Verify integrity with go.sum , which stores cryptographic hashes for each module version. Both go.mod and go.sum should be committed to version control.

7. Conclusion

Go Modules unify dependency management across the Go ecosystem, are tightly integrated into the Go toolchain, and are supported by all recent Go releases. Teams that have not yet migrated are strongly encouraged to adopt Go Modules for better developer experience, performance, and security.

Backend DevelopmentGodependency managementgo-modgo modulesgo.sumGOPATH
Tencent Cloud Developer
Written by

Tencent Cloud Developer

Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.