Go Basics #7 Packages and Modules (go mod)
The final basics post. Up to now your code has lived in a single file, but real projects involve many files and external libraries working together. This post covers Go’s code organization system.
Two units — packages and modules #
| Package | Module | |
|---|---|---|
| Unit | one folder | a bundle of folders |
| Definition | package <name> declaration | go.mod file |
| Analogy | class/namespace | project/library |
One folder = one package, multiple folders + go.mod = one module. The module is the larger unit.
Package — one folder is one unit #
Files in the same folder must all belong to the same package.
calculator/
├── add.go (package calculator)
├── multiply.go (package calculator)
└── helper.go (package calculator)All must start with package calculator. Mixing different package names is a compile error.
main package — runnable program #
package main
import "fmt"
func main() {
fmt.Println("hi")
}A folder with package main + func main() is a runnable program. Other packages are libraries — code that other code imports and uses.
Visibility — capitalized first letter #
Go’s visibility is very simple.
A name starting with a capital letter is exported (visible outside); lowercase means visible only within the package (unexported).
package math
// usable from outside
func Add(a, b int) int {
return a + b
}
// usable only in this package
func square(n int) int {
return n * n
}
// exported constant
const Pi = 3.14159
// internal constant
const initialBuffer = 1024There are no separate public/private keywords like other languages. You can tell visibility from the first letter alone.
import — bringing in another package #
Standard library #
import "fmt"
import "strings"
import "time"
// or grouped
import (
"fmt"
"strings"
"time"
)Multiple imports — the grouped form is recommended. Alphabetical sorting is done automatically by goimports.
External packages #
import (
"github.com/google/uuid"
"github.com/spf13/cobra"
)A GitHub-URL-style path becomes the import path directly. That’s why Go’s module system is URL-based.
Aliases and dot imports #
import (
f "fmt" // alias
. "strings" // dot — use names directly without the package name (not recommended)
_ "github.com/.../init" // blank identifier — side effects only (runs init)
)
f.Println("hi")Aliases help when names clash. Dot imports are almost never used because it becomes hard to tell where a function comes from. The blank identifier (_) is for when you only want a package’s init function to run (driver registration, etc.).
Module — go.mod
#
A bundle of multiple packages. The go.mod file is the entry point of a module.
mkdir myapp
cd myapp
go mod init github.com/curtis/myappA go.mod is created.
module github.com/curtis/myapp
go 1.22- module — the module’s import path. Other code uses it like
import "github.com/curtis/myapp/...". - go — minimum Go version used.
Module name — why a GitHub path? #
Go identifies modules by URL. Typically the GitHub/GitLab repository path becomes the module name as-is.
module github.com/curtis/myapp
module gitlab.com/team/service
module example.com/internal/utils // private domainEven if you have no plan to push to GitHub, you follow this format. When Go downloads a dependency it goes to that URL (when it’s a public repo).
A module with multiple packages #
myapp/
├── go.mod (module github.com/curtis/myapp)
├── main.go (package main)
├── auth/
│ ├── login.go (package auth)
│ └── register.go (package auth)
└── db/
└── connection.go (package db)Using other packages from main.go:
package main
import (
"fmt"
"github.com/curtis/myapp/auth"
"github.com/curtis/myapp/db"
)
func main() {
conn := db.Connect()
user := auth.Login("Curtis", "secret")
fmt.Println(conn, user)
}Packages within the same module — imported by module name + folder path.
Adding external packages — go get
#
go get github.com/google/uuidThis command:
- Downloads the package
- Adds the dependency to
go.mod - Records a checksum in
go.sum
module github.com/curtis/myapp
go 1.22
require github.com/google/uuid v1.6.0go.sum records exact versions of dependencies — guaranteeing that someone else builds with exactly the same versions.
Usage #
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New()
fmt.Println(id)
}go mod tidy — clean up
#
go mod tidyIt analyzes the code and:
- Adds dependencies used but missing from go.mod
- Removes dependencies in go.mod that aren’t used
A good habit to run at the end of every work session.
Dependency versions — semver #
require github.com/google/uuid v1.6.0semver (semantic versioning) — vMAJOR.MINOR.PATCH. The Go module system follows these rules.
- Major 0 — no stability guarantee (experimental)
- Major 1+ — compatibility guaranteed (minor/patch updates are compatible)
- Major 2+ — import path requires a
/v2,/v3, etc. suffix
import "github.com/some/lib/v2"When a library moves to v2, the import path itself changes. A design that enforces backward compatibility.
Updating versions #
# latest of a package
go get github.com/google/uuid@latest
# specific version
go get github.com/google/uuid@v1.5.0
# all dependencies to latest
go get -u ./...
# only minor/patch latest
go get -u=patch ./...Within a module — internal packages #
myapp/
├── go.mod
├── main.go
├── api/
│ └── handler.go (package api)
└── internal/
└── auth/
└── login.go (package auth)Packages inside an internal/ folder are importable only by that module or its descendants. External modules can’t import them.
When building a library — a standard pattern for separating the exposed API from implementation details.
Package init function #
Each package can have an init() function that runs automatically when the package is first imported.
package config
import "log"
var Settings map[string]string
func init() {
Settings = loadFromFile()
log.Println("config loaded")
}Used for side effects (loading settings, registering drivers). Multiple inits in one package are also allowed.
Avoid abusing it — an explicit initialization function (Init()) is usually clearer.
vendor folder — optional #
The go mod vendor command copies dependencies into the project.
go mod vendorAll dependency code lands in a vendor/ folder. The build pulls from there.
Pro: builds without external download (offline, private network). Con: the repository grows.
These days go.mod + the local cache ($GOPATH/pkg/mod) is enough, so vendor is rarely used.
replace — substitute with a local path
#
While developing a library — you can temporarily replace a real dependency with a local path.
require github.com/curtis/lib v1.0.0
replace github.com/curtis/lib => ../libCode in ../lib is used. Useful when developing a library and the app that uses it at the same time. Usually removed before committing.
Common commands at a glance #
go mod init <name> # new module
go mod tidy # tidy dependencies
go mod download # download specified dependencies only
go get <pkg> # add a dependency
go get -u <pkg> # update
go mod why <pkg> # why this dependency is needed
go mod graph # print dependency graph
go mod vendor # create vendor folderLightweight guide — package design #
The full list of design guidelines is hard to summarize, but a few that come up often:
- Package names are short and lowercase —
auth,db,config - If a package does many things, split it
- Cyclic dependency (A → B, B → A) is a compile error — a sign your design is tangled
- Minimize external exposure — start lowercase when possible
- Avoid catch-all packages like
util,common— intent isn’t visible
Wrap-up #
Through 7 basics posts we covered:
- Getting started and the first program — setup and Hello World (#1)
- Variables, types, constants —
:=, iota, untyped const (#2) - Control flow — for is the only loop, no fallthrough in switch (#3)
- Functions, multiple return, error —
if err != nil, defer (#4) - Collections — how slices work, comma-ok for maps (#5)
- Structs and methods — value/pointer receivers, embedding (#6)
- Packages and modules — visibility, go.mod, internal (this post)
With this you can confidently build small programs and CLI tools. The next Intermediate series covers Go’s real strengths — interfaces, concurrency (goroutines/channels), context, and the standard library.