Go Packages and Modules — go.mod, Import Paths and Package Visibility Explained
In this tutorial, you will learn about Go Packages and Modules. We cover key concepts, practical examples, and best practices to help you master this topic.
Go packages organize code into namespaces with exported (capitalized identifiers) and unexported (lowercase) visibility, while modules defined in go.md manage dependencies, versioning, and import paths.
What You'll Learn
- Creating and organizing packages
- Exporting and unexporting identifiers
- Module initialization with go.mod
- Managing dependencies
Why It Matters
Packages and modules define your project structure. Docker has hundreds of packages. Kubernetes uses complex module hierarchies. DodaZIP organizes archivers, compressors, and utilities into separate packages.
Real-World Use
Every Go project uses packages. Standard library packages (fmt, http, io) define the patterns. Third-party dependencies via modules. Proper package design is essential for maintainable Go code.
flowchart LR
A["Packages"] --> B["Exports"]
B --> C["go.mod"]
C --> D["Imports"]
D --> E["Dependencies"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Package Declaration
Every Go file starts with a package declaration:
// math/operations.go
package math
func Add(a, b int) int { return a + b }
func Subtract(a, b int) int { return a - b }
// cmd/main.go
package main
import "fmt"
import "example.com/myapp/math"
func main() {
fmt.Println(math.Add(5, 3)) // 8
}
Exported vs Unexported
Identifiers starting with uppercase are exported (public). Lowercase are unexported (private to the package):
package user
// Exported — accessible outside the package
type User struct {
Name string // Exported field
age int // Unexported field
}
// Exported function
func New(name string, age int) *User {
return &User{Name: name, age: age}
}
// Unexported function — internal helper
func validate(name string) error {
if name == "" {
return fmt.Errorf("name cannot be empty")
}
return nil
}
// Exported — but age is unexported, so external code can't set it
func (u *User) Age() int {
return u.age
}
Package Initialization
Packages can have init functions that run automatically:
package db
import (
"database/sql"
"log"
)
var DB *sql.DB
func init() {
var err error
DB, err = sql.Open("postgres", "postgres://localhost/mydb")
if err != nil {
log.Fatal("failed to connect:", err)
}
}
init order
// 1. Imported package init() runs first
// 2. Package-level variable initialization
// 3. Package's own init() runs
var Version = "1.0.0"
func init() {
fmt.Println("Package initialized, version:", Version)
}
Module Initialization
go mod init example.com/myapp
Creates go.mod:
module example.com/myapp
go 1.22
go.mod structure
module github.com/user/myapp
go 1.22
require (
github.com/gorilla/mux v1.8.1
github.com/lib/pq v1.10.9
)
// Indirect dependencies
require github.com/google/uuid v1.6.0 // indirect
Importing Packages
import (
// Standard library
"fmt"
"net/http"
"os"
// Third-party
"github.com/gorilla/mux"
"github.com/lib/pq"
// Local packages (within the module)
"example.com/myapp/internal/auth"
"example.com/myapp/pkg/handler"
)
Import Aliases
import (
"crypto/rand"
"math/rand"
mrand "math/rand" // Alias
)
func main() {
// Use alias for disambiguation
secure := rand.Read // crypto/rand
_ = mrand.Intn(10) // math/rand via alias
}
Blank Import
import _ "image/png" // Runs init() without using the package
Internal Package
The internal package restricts imports to the parent module:
myapp/
├── internal/
│ └── auth/
│ └── auth.go // Only importable by myapp/
├── pkg/
│ └── handler/
│ └── handler.go // Can import internal/auth
└── cmd/
└── server/
└── main.go // Can import internal/auth
// internal/auth/auth.go
package auth
// Only importable within myapp/
func ValidateToken(token string) bool {
return token == "valid"
}
Dependency Management
# Add a dependency
go get github.com/gorilla/mux@latest
# Update all dependencies
go get -u ./...
# Remove unused dependencies
go mod tidy
# Vendor dependencies
go mod vendor
# Verify dependencies
go mod verify
Using Dependencies
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", homeHandler)
http.ListenAndServe(":8080", r)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, modules!")
}
Organizing Packages
Recommended project layout:
myproject/
├── cmd/
│ ├── server/
│ │ └── main.go # Entry point
│ └── cli/
│ └── main.go # Another entry point
├── internal/
│ ├── auth/
│ │ └── auth.go # Internal package
│ └── db/
│ └── db.go # Internal package
├── pkg/
│ ├── api/
│ │ └── api.go # Public API package
│ └── config/
│ └── config.go # Public config package
├── go.mod
├── go.sum
└── README.md
Common Mistakes
1. Circular Imports
// package a imports package b
// package b imports package a // Error!
// Fix: extract shared types into a third package
2. Package Name Mismatch
// File: math/operations.go
package maths // Must be math (matches directory name)
// Go convention: package name = directory base name
3. Not Committing go.sum
// go.sum is needed for reproducible builds
// Always commit it to version control
4. Using relative imports
// Bad — relative import
import "../auth"
// Good — module-based import
import "example.com/myapp/internal/auth"
5. Not running go mod tidy
// go.mod has outdated/unused dependencies
// Run: go mod tidy
Practice Questions
1. What determines if an identifier is exported?
The first letter. Uppercase = exported (public). Lowercase = unexported (package-private).
2. What is the init function?
A function without parameters that runs automatically when a package is imported. Used for initialization, connection setup, registration. Runs once per package.
3. What is an internal package?
A package in a directory named internal that can only be imported by code in the parent module. Go enforces this restriction at compile time.
4. How do you add a dependency?
Run go get <package>@<version>. This updates go.mod and go.sum. Then import the package in your code.
Challenge: Create a multi-package project with a public API package, an internal auth package, and a main cmd that uses both.
Solution
Project structure:
myapp/
├── cmd/server/main.go
├── internal/auth/auth.go
├── pkg/api/handler.go
├── go.mod
└── go.sum
// go.mod
module example.com/myapp
go 1.22
// cmd/server/main.go
package main
import (
"fmt"
"net/http"
"example.com/myapp/internal/auth"
"example.com/myapp/pkg/api"
)
func main() {
handler := api.NewHandler(auth.NewAuthenticator())
http.HandleFunc("/", handler.HandleHome)
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}
// internal/auth/auth.go
package auth
import "fmt"
type Authenticator struct {
validTokens map[string]bool
}
func NewAuthenticator() *Authenticator {
return &Authenticator{
validTokens: map[string]bool{"secret123": true},
}
}
func (a *Authenticator) Validate(token string) bool {
return a.validTokens[token]
}
func (a *Authenticator) Login(username, password string) (string, error) {
if username == "admin" && password == "pass" {
token := "secret123"
a.validTokens[token] = true
return token, nil
}
return "", fmt.Errorf("invalid credentials")
}
// pkg/api/handler.go
package api
import (
"fmt"
"net/http"
"example.com/myapp/internal/auth"
)
type Handler struct {
auth *auth.Authenticator
}
func NewHandler(auth *auth.Authenticator) *Handler {
return &Handler{auth: auth}
}
func (h *Handler) HandleHome(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !h.auth.Validate(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Welcome, authenticated user!")
}
FAQ
{{< faq question="What's the difference between a package and a module?" >}} A package is a directory of Go files with the same package declaration. A module is a collection of packages with a go.mod file defining the module path and dependencies. {{< /faq >}}
{{< faq question="Should I use relative or absolute import paths?" >}}
Always use absolute paths based on the module path: example.com/myapp/pkg/config. Never use relative imports like ../pkg/config.
{{< /faq >}}
{{< faq question="What is go.sum?" >}} A file containing checksums of all dependency modules. It ensures reproducible builds by verifying dependency integrity. Commit it to version control. {{< /faq >}}
{{< faq question="Can I have multiple packages in one directory?" >}} No. All .go files in a directory must belong to the same package. Different packages go in different directories. {{< /faq >}}
{{< faq question="What is the blank identifier import for?" >}}
import _ "image/png" runs the package's init() function without importing any identifiers. Used for driver registration (database drivers, image formats).
{{< /faq >}}
Try It Yourself
mkdir -p myapp/{cmd/server,internal/auth,pkg/greeter}
cd myapp
go mod init example.com/myapp
// pkg/greeter/greeter.go
package greeter
import "fmt"
func Hello(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
func goodbye(name string) string {
return fmt.Sprintf("Goodbye, %s!", name)
}
// internal/auth/auth.go
package auth
import "fmt"
func CheckAccess(role string) bool {
return role == "admin"
}
func logAttempt(role string) {
fmt.Println("Access attempt with role:", role)
}
// cmd/server/main.go
package main
import (
"fmt"
"example.com/myapp/internal/auth"
"example.com/myapp/pkg/greeter"
)
func main() {
msg := greeter.Hello("Alice")
fmt.Println(msg)
if auth.CheckAccess("admin") {
fmt.Println("Access granted")
} else {
fmt.Println("Access denied")
}
}
go run ./cmd/server/
# Hello, Alice!
# Access granted
What's Next
Now that you understand packages and modules, learn about testing and benchmarking in Go.
| Topic | Description | Link |
|---|---|---|
| Go Testing | Table tests, benchmarks, coverage | {{< ref "16-testing" >}} |
| Go Error Handling | Error interface, wrapping, sentinel | {{< ref "13-error-handling" >}} |
| Rust Cargo | Compare Rust's package manager | Rust |