How Go Code Is Organized
Overview
Go has strong opinions about code organization. Understanding packages, imports, and project structure is essential for writing maintainable Go programs and for your code to work correctly with the Go toolchain.
Packages: The Building Blocks
Every Go file belongs to a package. Packages group related code and control visibility.
// File: greet.go
package greeting // Package declaration (first line)
import "fmt"
func Hello(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}Package Rules
| Rule | Description |
|---|---|
| All files in a directory belong to the same package | You can’t have package foo and package bar in the same folder |
| Package name matches directory name (by convention) | Directory server/ contains package server |
package main is special |
It defines an executable, not a library |
The main Package
Only package main can produce an executable. It must have a main() function:
package main
import "fmt"
func main() {
fmt.Println("This is the entry point")
}Import System
Basic Imports
import "fmt" // Standard library
import "net/http" // Nested standard library package
import "github.com/user/repo" // External packageImport Block (Preferred Style)
import (
// Standard library (grouped first)
"fmt"
"net/http"
// Third-party packages (blank line separator)
"github.com/gin-gonic/gin"
// Internal packages (blank line separator)
"myproject/internal/config"
)Import Aliases
import (
"crypto/rand"
mrand "math/rand" // Alias to avoid collision
)
// Usage
cryptoBytes, _ := rand.Read(buf)
randomInt := mrand.Intn(100)Blank Imports (Side Effects Only)
import (
"database/sql"
_ "github.com/lib/pq" // Import for side effects (driver registration)
)Dot Imports (Avoid in Production)
import . "fmt"
// Now you can use Println directly
Println("Hello") // Instead of fmt.PrintlnVisibility: Exported vs Unexported
Go uses capitalization to control visibility:
package user
// Exported (public) - starts with uppercase
type User struct {
Name string // Exported field
Email string // Exported field
age int // unexported field (private)
}
// Exported function
func CreateUser(name string) *User {
return &User{Name: name}
}
// unexported function (private)
func validate(u *User) bool {
return len(u.Name) > 0
}| Name | Visibility |
|---|---|
User |
Exported (accessible from other packages) |
Name |
Exported field |
age |
Unexported (only accessible within user package) |
CreateUser |
Exported function |
validate |
Unexported function |
Modules: Modern Dependency Management
A module is a collection of packages with a go.mod file at the root.
Creating a Module
$ mkdir myproject && cd myproject
$ go mod init github.com/username/myprojectThis creates go.mod:
module github.com/username/myproject
go 1.26
Module Structure
github.com/username/myproject/
├── go.mod
├── go.sum
├── main.go
├── config/
│ └── config.go # package config
├── internal/
│ └── database/
│ └── db.go # package database (private)
└── pkg/
└── api/
└── api.go # package api (public)
Internal Packages
The internal/ directory has special meaning: packages inside it can only be imported by code within the same module tree.
myproject/
├── internal/
│ └── secret/
│ └── secret.go # Only myproject can import this
└── cmd/
└── app/
└── main.go # Can import internal/secret
// This works (same module)
import "github.com/username/myproject/internal/secret"
// This fails (different module trying to import internal)
// Error: use of internal package not allowedStandard Project Layout
While Go doesn’t mandate a structure, this layout is widely adopted:
myproject/
├── cmd/ # Main applications
│ ├── myapp/
│ │ └── main.go
│ └── worker/
│ └── main.go
├── internal/ # Private packages
│ ├── config/
│ ├── database/
│ └── service/
├── pkg/ # Public libraries (optional)
│ └── api/
├── api/ # OpenAPI/Swagger specs
├── web/ # Web assets
├── scripts/ # Build/install scripts
├── docs/ # Documentation
├── go.mod
├── go.sum
└── README.md
cmd/ Directory
Each subdirectory in cmd/ becomes a separate binary:
$ go build ./cmd/myapp
$ go build ./cmd/workerPackage Naming Conventions
| ✅ Good | ❌ Bad | Why |
|---|---|---|
user |
userPackage |
Avoid redundant suffixes |
http |
httputil |
Only add suffix if necessary |
strconv |
stringconversion |
Short but clear |
io |
inputoutput |
Abbreviations OK if common |
Avoid Meaningless Names
util→ Use specific names likestringutil,timeutilcommon→ Be specific about what’s commonmisc→ Break into focused packages
Circular Import Prevention
Go prohibits circular imports. If you have:
package a → imports → package b
package b → imports → package a // Error!
Solutions: 1. Extract common code to a third package 2. Use interfaces to break the dependency 3. Restructure your packages
Summary
| Concept | Purpose |
|---|---|
| Package | Groups related code, controls visibility |
| Module | Collection of packages with version info |
internal/ |
Private packages within a module |
| Uppercase | Exported (public) |
| Lowercase | Unexported (private) |
Exercises
- Create a module with two packages and have one import the other
- Create an
internal/package and verify external code can’t import it - Create a project with
cmd/containing multiple applications