Learning Golang - Modularization, Packages, and Dependency Management
Episode 4 of 19

Learning Golang - Modularization, Packages, and Dependency Management

This episode teaches you how to organize code as Go modules: go mod init, go mod tidy, and go mod verify. You will learn how to import local and external packages, manage module versions, write reusable packages, and document them with go doc.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

You already mastered single-file programs in episode 3. Now it's time to organize code as a real project through modularization. The larger the application, the more important it is to separate code into packages with clear responsibilities.

Episode 4 covers the Go module system as a whole: creating a module with go mod init, importing local and external packages, managing dependency versions, writing reusable packages with correct visibility, and documenting them through go doc. By the end of this episode you will have a tidy, maintainable project structure.

Creating a Go Module

go mod init

A module is the distribution unit that bundles many packages together. Create your project directory, then initialize the module with a unique path. For public projects use a path like github.com/user/nama-project; for private projects you can use a simple name.

Initializing a module
mkdir kalkulator
cd kalkulator
go mod init github.com/arman/kalkulator
cat go.mod

The generated go.mod file contains the module path and the Go version. This module name determines the import path of every package inside it — choose it wisely, because it is hard to change once code is distributed.

go mod tidy

The go mod tidy command synchronizes the go.mod and go.sum files with the actual code: it adds the dependencies that are needed, removes unused ones, and records the checksum of every module.

Synchronize dependencies
go mod tidy
ls go.sum

go.sum contains the cryptographic hash of every dependency. Whenever a dependency changes, the hash is updated. Don't edit go.sum manually.

Importing Local Packages

Directory Structure as Packages

Every subdirectory containing Go files is a package. The package name comes from the package declaration inside the files, not from the directory name — although by convention they match.

Project structure
kalkulator/
├── go.mod
├── main.go
└── operasi/
    └── operasi.go

Importing with the Module Path

Local packages are imported using the combination of the module path and the subdirectory name:

operasi/operasi.go
package operasi
 
func Tambah(a, b int) int {
	return a + b
}
main.go
package main
 
import (
	"fmt"
 
	"github.com/arman/kalkulator/operasi"
)
 
func main() {
	fmt.Println(operasi.Tambah(2, 3))
}

Identifiers that start with a capital letter, like Tambah, are exported and can be used from other packages. Lowercase identifiers such as jumlahInternal are only visible inside the same package. This is Go's visibility system: explicit and simple.

External Dependencies

Adding Third-Party Packages

To add an external dependency, use go get with the path and an optional version:

Add a dependency
go get github.com/google/uuid
go get github.com/google/uuid@v1.6.0

Without a version, Go uses the newest compatible version. With @v1.6.0, the version is pinned. Afterwards, go mod tidy will synchronize the module files and add the version that is actually used.

Avoiding Unstable Versions

Semantic versioning applies in Go: modules v2 and above must change their module path with a suffix like /v2. Versions below v1.0.0 are considered unstable and can change semantically. When choosing dependencies, prioritize ones that are stable and well-maintained.

Writing Reusable Packages

Documentation with go doc

Go treats comments as documentation: comments that begin with the identifier name become its official documentation. This is the convention go doc relies on.

Package with documentation
// Package operasi menyediakan operasi aritmatika dasar.
package operasi
 
// Tambah menjumlahkan dua bilangan bulat.
func Tambah(a, b int) int {
	return a + b
}

Run go doc to view the documentation from the terminal:

View documentation
go doc ./operasi
go doc operasi.Tambah

The go doc operasi.Tambah command displays the documentation comment of the Tambah function along with its signature.

Preparing a README and License

For public packages, add a README.md, a license file, and release versions. The module path must match the repository location so other people can import it. This consistency matters when you start distributing code, which we will cover in episode 17.

Handling Legacy Code and Vendoring

go mod vendor

For pipelines that need deterministic builds without internet access, use go mod vendor to copy all dependencies into a vendor/ directory. Subsequent builds use -mod=vendor automatically when that directory exists.

Vendor dependencies
go mod vendor
go build -mod=vendor ./...

go mod verify

go mod verify checks whether the dependencies stored in the module cache still match the checksums in go.sum. Run this command in your pipeline before a release to make sure no files have been modified:

Verify checksums
go mod verify

Project Structure Best Practices

Some widely used conventions in industry:

  • Put the entry point at the root or in a cmd directory.
  • Keep internal libraries in an internal directory so they cannot be imported from outside the module.
  • Separate HTTP handlers, service/business logic, and repository/data access.
  • Avoid utils packages that become dumping grounds; name packages after their responsibility.

We will keep using this structure from episode 7 to the end of the series, as your application grows from a calculator into an HTTP service with a database.

Closing

Episode 4 transformed you from a script writer into a Go project manager: creating modules with go mod init, synchronizing dependencies with go mod tidy, importing local and external packages, writing reusable packages with capital-letter visibility, documenting with go doc, and securing dependencies with go mod verify.

Key takeaways:

  • go mod init determines the import path of every package in the module.
  • go mod tidy keeps go.mod and go.sum always in sync.
  • Capitalized identifiers are exported; lowercase ones are private.
  • go doc uses comments as official documentation.
  • Stable versions use semantic versioning with a /v2 suffix for majors.
  • go mod vendor and go mod verify keep builds deterministic.

In the next episode we will discuss data types, structs, interfaces, and generics — structs as data models with field tags and method receivers, idiomatic duck-typing interfaces, and modern generics with type constraints and reusable algorithms. This forms the data modeling toolkit you will use in every episode that follows.

Learning Golang - Modularization, Packages, and Dependency Management | Learning Golang