Go Project Workspace Structure
In this page:
A Minimal Layout
The simplest possible Go project is just a go.mod file and a main.go in the same directory. As a project grows, code is split into subdirectories, each subdirectory becoming its own importable package named after its folder.
Example: A Minimal Layout
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// main is where the program starts running
func main() {
// Print the values followed by a newline
fmt.Println("myapp/")
// Print the values followed by a newline
fmt.Println(" go.mod")
fmt.Println(" main.go")
}
Login to try C/C++/Java/PHP code in the editor
Organizing Packages by Folder
Each directory containing .go files (other than the root) typically represents one package, imported by its path relative to the module root, such as github.com/example/myapp/utils.
This convention keeps related code together and makes the import graph easy to follow just by looking at the file tree.
Example: Organizing Packages by Folder
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// main is where the program starts running
func main() {
// Short declaration: create layout and infer the type
layout := []string{"myapp/go.mod", "myapp/main.go", "myapp/utils/strings.go"}
// Loop over layout; the blank identifier _ ignores the index
for _, path := range layout {
// Print the values followed by a newline
fmt.Println(path)
}
}
Login to try C/C++/Java/PHP code in the editor
The internal/ Convention
Go treats any package inside a directory named internal specially: it can only be imported by code rooted at the parent of that internal directory.
This is the standard way to mark packages as private implementation details that outside modules should not depend on.
Example: The internal/ Convention
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// main is where the program starts running
func main() {
// Print the values followed by a newline
fmt.Println("myapp/internal/auth can only be imported from within myapp")
}
Login to try C/C++/Java/PHP code in the editor
Where Tests Live
Go test files sit right next to the code they cover and are named with a _test.go suffix, such as strings_test.go alongside strings.go.
The 'go test' tool automatically finds and runs every _test.go file in a package, so there's no separate test-discovery configuration needed.
Example: Where Tests Live
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// main is where the program starts running
func main() {
// Print the values followed by a newline
fmt.Println("utils/strings.go")
// Print the values followed by a newline
fmt.Println("utils/strings_test.go")
}
Login to try C/C++/Java/PHP code in the editor
- Putting all code in package main inside one giant file instead of splitting reusable logic into separate packages.
- Creating a top-level src folder out of old GOPATH habit, which modern Go modules don't require or expect.
- Mixing test files, main application code, and documentation with no consistent naming, making 'go build ./...' behave unpredictably.
- A typical Go project has a go.mod at its root and a main.go (or a cmd/ directory) as the entry point.
- Reusable logic is organized into small, focused packages, each in its own directory.
- The 'internal/' directory name has special meaning: it restricts imports to within the module.
- Test files live alongside the code they test, named with a _test.go suffix.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: