GO

Go Fundamentals

18 lessons

Progress0%
1. Introduction to Go
1What is Go?
2. Variables and Data Types
1Data Types in Go
3. Control Flow
If, For, and SwitchDefer, Panic, Recover
4. Functions
Function BasicsError Handling
5. Structs and Methods
StructsMethods and Interfaces
6. Concurrency
Goroutines and ChannelsSelect and Sync
7. Maps & Slices Advanced
Slices Deep DiveMaps Operations & Patterns
8. Interfaces Deep Dive
Interface Composition & anyCommon Interfaces & Patterns
9. Packages & Modules
Package SystemGo Modules & Workspace
10. Testing & Standard Library
Testing in GoStandard Library Essentials
All Tutorials
GoPackages & Modules
Lesson 16 of 18 min
Chapter 9 · Lesson 2

Go Modules & Workspace

Go Modules & Workspace

Go modules (introduced in Go 1.11, default since Go 1.16) are the official dependency management system. A module is a collection of Go packages with a defined module path and versioned dependencies.

go.mod — The Module File Every module has a go.mod file at its root:

code
module github.com/yourname/yourproject

go 1.22

require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.6.0
)
  • module: the module path — used as the import prefix for all packages in the module
  • go: minimum Go version this module requires
  • require: direct dependencies with minimum versions (Go uses Minimum Version Selection)

go get — Adding Dependencies

bash
go get github.com/some/package@v1.2.3  # specific version
go get github.com/some/package@latest  # latest tagged version
go get github.com/some/package@main    # specific branch

This updates go.mod and go.sum automatically.

go mod tidy Removes unused dependencies and adds missing ones. Run after editing imports:

bash
go mod tidy

go.sum — The Checksum File go.sum contains cryptographic hashes of every dependency version, ensuring builds are reproducible and tamper-resistant. Commit both go.mod and go.sum to version control.

Semantic Versioning Go modules use semver (v1.2.3). Major versions ≥ 2 require a path suffix:

go
import "github.com/user/pkg/v2"

This allows v1 and v2 to coexist in the same build.

The replace Directive Use replace to swap a dependency with a local fork or a different version:

code
replace github.com/original/pkg => ../local-fork
replace github.com/original/pkg v1.2.3 => github.com/fork/pkg v1.2.4

Useful for local development and testing patches before upstreaming.

Multi-Module Workspaces (go.work) Go 1.18 introduced workspaces for working on multiple modules simultaneously without publishing:

code
go 1.22

use (
    ./myapp
    ./mylib
)

go work sync syncs the workspace; go work use ./newmodule adds a module.

GOPATH vs Modules Before modules, all Go code lived under $GOPATH/src. Modules freed Go from GOPATH: you can place projects anywhere on the filesystem. GOPATH is still used for the module cache ($GOPATH/pkg/mod) and installed binaries ($GOPATH/bin).

Vendor Directory go mod vendor copies all dependencies into a vendor/ directory for offline builds or auditing:

bash
go mod vendor
go build -mod=vendor ./...

Code Examples

Annotated go.mod filego
// This is an annotated go.mod — not valid Go syntax,
// but shows the structure of a real go.mod file.
//
// File: go.mod (lives at the root of your module)

// module declares this module's canonical import path.
// All packages in this repo are imported as:
//   github.com/example/myapp/pkg/something
module github.com/example/myapp

// go specifies the minimum Go toolchain version.
// Features above this version will cause compile errors
// on older toolchains.
go 1.22

// require lists direct and indirect dependencies.
// Go uses Minimum Version Selection (MVS): it picks the
// minimum version that satisfies all constraints.
require (
    github.com/gin-gonic/gin v1.9.1
    golang.org/x/sync v0.6.0

    // indirect: required transitively, not directly imported
    github.com/bytedance/sonic v1.10.0 // indirect
)

// replace swaps a module for a local fork during development
// replace github.com/original/lib => ../local-lib

// exclude prevents a specific known-bad version from being used
// exclude github.com/bad/pkg v1.0.0

go.mod is the module manifest. The module path is the import prefix for all packages in the module. go mod tidy keeps require entries accurate. Commit go.mod and go.sum together.

go get and importing external packagesgo
package main

// After running: go get golang.org/x/text/cases
// go.mod gains:  require golang.org/x/text v0.14.0
// go.sum gains:  hash entries for the module

// Standard library imports — no module needed
import (
    "fmt"
    "strings"
)

// Simulating what an external package import looks like
// (golang.org/x/text/cases would provide Unicode-aware casing)

// Without the real package, we demonstrate the pattern:
func titleCase(s string) string {
    // Real code would use: cases.Title(language.English).String(s)
    words := strings.Fields(s)
    for i, w := range words {
        if len(w) > 0 {
            words[i] = strings.ToUpper(w[:1]) + strings.ToLower(w[1:])
        }
    }
    return strings.Join(words, " ")
}

func main() {
    // go get workflow:
    // 1. go get github.com/some/pkg@v1.2.3
    // 2. import "github.com/some/pkg" in your code
    // 3. go mod tidy  (removes unused, adds missing)
    // 4. go build ./...

    samples := []string{
        "hello world",
        "the quick brown fox",
        "go modules are great",
    }

    for _, s := range samples {
        fmt.Printf("%-30s -> %s\n", s, titleCase(s))
    }
}

go get updates go.mod and go.sum automatically. go mod tidy removes unused dependencies. Always commit go.sum alongside go.mod for reproducible builds.

replace directive and workspace patterngo
package main

import "fmt"

// Demonstrating the replace directive pattern.
//
// Scenario: your app depends on github.com/acme/logger,
// but you need to patch it locally before the fix is merged.
//
// go.mod entries:
//
//   require github.com/acme/logger v1.3.0
//
//   replace github.com/acme/logger => ../logger-fork
//
// go.work (workspace file) for multi-module development:
//
//   go 1.22
//   use (
//       ./myapp
//       ./logger-fork
//   )
//   // No replace needed — workspace redirects automatically

// Simulating the replaced package behaviour
type Logger interface {
    Info(msg string)
    Error(msg string)
}

// This would normally come from the replaced module
type PatchedLogger struct{ prefix string }

func (l *PatchedLogger) Info(msg string) {
    fmt.Printf("[INFO]  %s: %s\n", l.prefix, msg)
}
func (l *PatchedLogger) Error(msg string) {
    fmt.Printf("[ERROR] %s: %s\n", l.prefix, msg)
}

func NewLogger(prefix string) Logger {
    return &PatchedLogger{prefix: prefix}
}

func main() {
    log := NewLogger("app")
    log.Info("starting up")
    log.Info("loaded config")
    log.Error("database connection failed")

    fmt.Println()
    fmt.Println("Module commands cheat-sheet:")
    fmt.Println("  go mod init github.com/you/project")
    fmt.Println("  go get github.com/pkg@v1.2.3")
    fmt.Println("  go mod tidy")
    fmt.Println("  go mod vendor")
    fmt.Println("  go work init ./app ./lib")
}

The replace directive redirects a module import to a local path or a different remote version. go.work workspaces extend this to multiple modules without modifying go.mod, keeping changes local to the developer.

Quick Quiz

1. What is the purpose of the go.sum file?

2. What does `go mod tidy` do?

3. Why does a major version v2+ module require a path suffix like `/v2`?

4. What is the role of the `replace` directive in go.mod?

Was this lesson helpful?

PreviousNext