Packages and Project Structure
How Go organizes code into packages, and the conventional project layout most Go codebases follow.
3 min read
Every Go file belongs to exactly one package, and packages are Go's only unit of code organization — there's no nested namespace system, no sub-modules within a module beyond directory structure itself.
Packages are directories
A package is simply a directory of .go files that all declare the same package name at the top:
// file: mathutils/add.go
package mathutils
func Add(a, b int) int {
return a + b
}// file: mathutils/subtract.go
package mathutils
func Subtract(a, b int) int {
return a - b
}Both files live in a mathutils/ directory and share the mathutils package — Go doesn't care how you split functionality across files within a package, only that they agree on the package name.
Importing a package
import "github.com/yourname/yourproject/mathutils"
func main() {
result := mathutils.Add(3, 4)
}The import path is built from the module path (declared in go.mod, covered in the installation lesson) plus the directory path to the package. Only exported names (capitalized, per the visibility rule from the variables lesson) are accessible after import — mathutils.add would be invisible outside its own package.
A conventional project layout
Go doesn't enforce a specific project structure the way some frameworks do, but a de facto convention has emerged for anything beyond a trivial script:
myproject/
├── go.mod
├── go.sum
├── main.go // or cmd/myproject/main.go for multiple binaries
├── internal/
│ ├── handler/ // HTTP handlers
│ ├── service/ // business logic
│ └── repository/ // data access
├── pkg/ // code intended for external use by other projects
└── cmd/
└── myproject/
└── main.go
The internal/ convention
internal/ isn't just a naming convention — it's enforced by the Go compiler itself. Any package under a directory named internal/ can only be imported by code within the same module tree rooted at internal's parent, never by an external project:
myproject/
└── internal/
└── auth/
└── auth.go // importable from anywhere inside myproject, nowhere outside it
This gives you a genuine, compiler-enforced way to say "this is an implementation detail, not part of our public API" — useful the moment your project is a library other people import, and a good habit even in application code as a way of clearly separating internal wiring from anything meant to be reused.
Package naming conventions
A few habits are near-universal in Go: package names are short, lowercase, single words with no underscores (httputil, not http_util or HTTPUtil). The package name shouldn't stutter with the names inside it — user.User reads awkwardly compared to just models.User or a well-chosen user.Profile. And a package's name should describe what it provides, not what it contains — strings, not stringutils or helpers, is the standard library's own example of getting this right.
Understanding packages, imports, and this layout is what lets a Go project scale from one main.go file into a real multi-package application without the organization becoming ad hoc — which sets up naturally for the next lesson's look at the frameworks built on top of these conventions.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.