Intermediate · Packages, testing and generics · topic 21 of 46 · 35 min
Packages, visibility and project layout
Explanation
Why split code into packages?
A 3,000-line main.go is hard to read, test and share. Packages group related code: everything about invoices in one package, everything about the database in another. Each package has a clear job and a small public surface (the names other packages can use).
One folder, one package
- All .go files in one folder must have the same package name (test files may also use name_test).
- The package name is usually the last part of the folder path: folder internal/invoice has package invoice.
- Package names are short, lowercase, one word, no underscores: invoice, store, httpapi. Avoid vague names like util, common or helpers.
- You import a package by its full path: the module path plus the folder, for example "example.com/billing/internal/invoice".
billing/
├── go.mod module example.com/billing
├── main.go package main
└── internal/
└── invoice/
├── invoice.go package invoice
└── invoice_test.go// internal/invoice/invoice.go
package invoice
// GSTPercent is exported: other packages can read it.
const GSTPercent = 18
// Total is exported. It returns the amount with GST, in paise.
func Total(paise int64) int64 {
return paise + gst(paise)
}
// gst is unexported: only code in package invoice can call it.
func gst(paise int64) int64 {
return paise * GSTPercent / 100
}// main.go
package main
import (
"fmt"
"example.com/billing/internal/invoice"
)
func main() {
fmt.Println(invoice.Total(10000)) // 11800
// invoice.gst(100) // compile error: gst not exported
}Visibility: just the first letter
Go has no public, private or protected keywords. An identifier (a name of a type, function, variable, constant, field or method) that starts with an upper-case letter is exported. Anything else is visible only inside its own package. The same rule applies to struct fields, which matters for JSON (module 7).
internal/: a wall the compiler enforces
Code inside a folder named internal can only be imported by packages rooted at the parent of internal. In billing/internal/invoice, only packages under billing/ can import it. Other modules cannot, even if your repository is public. Use internal/ for everything you do not want others to depend on.
A sensible layout, without over-engineering
| Project size | Layout |
|---|---|
| Small tool or learning project | go.mod and main.go (plus a few more .go files) in one folder |
| Service with some logic | main.go at the root, packages under internal/ |
| Several binaries (api, worker, cli) | cmd/api/main.go, cmd/worker/main.go, shared code in internal/ |
Other rules worth knowing
- Import cycles are not allowed: if package a imports b, then b cannot import a. Fix it by moving the shared piece into a third package or by using an interface.
- func init() runs automatically before main, after package-level variables are set. Keep init small; hidden start-up work is hard to test.
- A blank import, import _ "github.com/jackc/pgx/v5/stdlib", runs a package's init only. Database drivers use this to register themselves (module 8).
- You can rename an import: import ex "example.com/billing/internal/invoice". Use it only to fix name clashes.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Packages, visibility and project layout · 1/7
As projects grow, we split code into packages. Each package is one folder with one job.
Prefer a video by a teacher? Find free videos on “golang packages exported names internal folder project structure” on YouTube ↗
In real life: Splitting an expense tracker
A student's expense tracker started as one 900-line main.go. She splits it into internal/expense (types and rules), internal/store (saving to a file) and main.go (reading commands). Tests for the expense rules now run without touching files, and a friend can read each package in five minutes.
expenses/
├── go.mod module example.com/expenses
├── main.go wires everything together
└── internal/
├── expense/
│ ├── expense.go type Expense, func Validate, func Total
│ └── expense_test.go
└── store/
└── file.go func Load(path) / func Save(path, list)Practice (0/4 done)
Which function can another package call?
Module example.com/shop has shop/internal/pay. Can module example.com/other import it?
Package a imports b, and you add an import of a inside b. What happens?
Create a module example.com/marks with a package internal/grade that exports func Letter(marks int) string and keeps a helper unexported. Call grade.Letter from main.go.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Dependencies with Go modulesPackages, testing and generics · same module
- Testing with go testPackages, testing and generics · same module
- Benchmarks and fuzz testsPackages, testing and generics · same module
- Packages and access modifiersJava Programming (Core to Spring Boot) · Inheritance, polymorphism and interfaces
- Modules, npm and ViteFrontend Web Development · Async JavaScript, modules and tools
- Modules: import and exportJavaScript Programming (language in depth) · Modules, errors, JSON and dates
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What decides whether a Go name is exported?
Answer: Whether its first letter is upper-case
Upper-case first letter means exported.
What is the usual package name for files in folder internal/store?
Answer: store
By convention the package name matches the last part of the folder path.
Why might you write import _ "some/driver"?
Answer: To run its init (for example to register a database driver) without using its names
A blank import is for side effects only.
Is the popular "project-layout" repository an official Go standard?
Answer: No; the Go team recommends starting simple
It is a community repo, not an official standard.
Which modifier allows access from subclasses in other packages but not from unrelated classes there?Related
In import formatPrice, { addGst } from './money.js'; which one is the default export?Related
Answer: formatPrice
The name outside the braces is the default export. Names inside { } are named exports.
Which function can be used from another package?Related
Answer: CalculateTax
Names starting with a capital letter are exported.
How many default exports can a module have?Related
Answer: At most one
One default export per module; named exports are unlimited.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Packages, testing and generics” before its exam: Dependencies with Go modules, Testing with go test, Benchmarks and fuzz tests, Generics: type parameters and constraints.
- Module 5 exam: Packages, testing and generics · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Concurrency: goroutines, channels and context. 46 topics left in the course before the final exam.
← Previous: panic and recover: only for the truly unexpected