Introduction
From JS to Go, I wasn’t very familiar with how Go modules work. Although there are similarities, the experience feels very simple, even minimalistic at times.
Initializing a Go Module
# Initialize a Go Module: The Module Path must be unique, typically using a GitHub Repo path.go mod init github.com/riceball-tw/project
# Install a Go Modulego get github.com/fatih/colorYou will get the following go.mod and go.sum plain text files to record module dependencies and the Go version. Packages that are not directly referenced will be marked as indirect:
module github.com/riceball-tw/project
go 1.25.6
require ( github.com/fatih/color v1.18.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/sys v0.25.0 // indirect)main is the entry point
All compiled Go programs will always start by executing main() in package main — no exceptions. Multiple main packages can exist within the same module.
package main
func main()Creating different packages
By convention, folder names are usually the package names, but it’s not mandatory.
greetings/└── greetings.gogo.modgo.summain.gopackage greetings
import "fmt"
func Hello(name string) { fmt.Printf("Hello %s!", name)}Capitalization controls whether variables are exported
A clever design in Go is that the capitalization of variables or functions controls whether other packages can access them. It’s a very unique approach without extra keywords or conventions.
- Uppercase first letter: indicates exported (public), accessible by other packages
- Lowercase first letter: indicates unexported (private), usable only within the same package
Importing other packages
By using import to combine the module name and package path, you can use identifiers from that package:
package main
import "github.com/riceball-tw/project/greetings"
func main() { greetings.Hi("test")}Importing with package aliases
You can also use an alias to simplify a long package name:
import g "github.com/riceball-tw/project/greetings"func main() { g.Hello("test")}Internal package
Go has a special internal directory mechanism. Packages placed under an internal directory can only be imported by code in the parent directory and its subdirectories:
myproject/├── internal/│ └── helper/│ └── helper.go # It can only be used by code within myproject package.├── greetings/│ └── greetings.go└── main.goPackage testing
Go’s testing tools are built into the language. Test files must end with _test.go, and test functions must start with Test and accept a *testing.T parameter.
package greetings
// Hello is public functionfunc Hello(name string) string { return formatMessage(name)}
// formatMessage is private functionfunc formatMessage(name string) string { return "Hello " + name + "!"}package greetings // Note: no _test
func TestHello(t *testing.T) { Hello("World") // ✅ Can be call}
func TestFormatMessage(t *testing.T) { formatMessage("World") // ✅ Private function can be call}package greetings_test // Note: Has _test
import "github.com/riceball-tw/project/greetings"
func TestHello(t *testing.T) { greetings.Hello("World") // ✅ Can be call}
func TestFormatMessage(t *testing.T) { greetings.formatMessage("World") // ❌ Compile error! Can not access private function}Go provides a unique testing approach: if you use package packagename_test (with the _test suffix) in a test file, the test code can only access the package’s public API, simulating an external user’s perspective:
- Enforces testing via public API: ensures tests are written from a user’s viewpoint and only test the public interface
- Avoids testing implementation details: private functions are inaccessible, preventing over-coupling between tests and implementation
- Better encapsulation: if internal implementation changes but the public API remains the same, tests don’t need to change
Summary
- A module can contain multiple packages
- Package access is controlled by capitalization: uppercase for exported, lowercase for unexported
- Every executable Go program must have a
mainpackage and amain()function go.modmanages dependencies;go.summanages package checksums- The
internaldirectory provides private package mechanisms within a module