Guide to Organizing Go Project Structure, Packages, and Commands
This article explains how to design a Go project layout, differentiate internal and external packages, configure modules, write simple functions, import packages, create command‑line tools, and manage private code, providing practical code examples and best‑practice recommendations for backend developers.
Go has become one of the most popular languages for cloud development, and organizing a Go project’s directory structure is a common challenge. This guide walks through project layout, the distinction between internal and external packages, and how to structure command‑line tools.
Key questions addressed:
How does the project structure reflect import paths?
How to organize command‑line tools alongside code?
How to flexibly arrange code across modules?
How can multiple packages coexist in a single module?
How do multiple packages live together in one module?
Terminology: An internal package is private to the module and cannot be imported by external code.
Example project layout (modlib):
├── LICENSE
├── README.md
├── config.go
├── go.mod
├── go.sum
├── clientlib
│ ├── lib.go
│ └── lib_test.go
├── cmd
│ ├── modlib-client
│ │ └── main.go
│ └── modlib-server
│ └── main.go
├── internal
│ └── auth
│ ├── auth.go
│ └── auth_test.go
└── serverlib
└── lib.goThe go.mod file defines the module name (e.g., module github.com/qidian/modlib ) and, in this example, has no dependencies.
Simple function example (config.go):
package modlib
func Config() string {
return "modlib config"
}Importing this module from another package looks like:
package main
import "fmt"
import "github.com/qidian/modlib"
func main() {
fmt.Println(modlib.Config())
}The clientlib package provides its own function:
package clientlib
func Hello() string {
return "clientlib hello"
}Using both modlib and clientlib together:
package main
import "fmt"
import "github.com/qidian/modlib"
import "github.com/qidian/modlib/clientlib"
func main() {
fmt.Println(modlib.Config())
fmt.Println(clientlib.Hello())
}The cmd directory holds executable commands. After installing with go get github.com/qidian/modlib/cmd/modlib-client , the binary can be run directly.
$ go get github.com/qidian/modlib/cmd/modlib-client
$ modlib-client
Running client
Config: modlib config
clientlib helloSimilarly, the server command demonstrates importing multiple packages, including an internal package:
package main
import (
"fmt"
"github.com/qidian/modlib"
"github.com/qidian/modlib/internal/auth"
"github.com/qidian/modlib/serverlib"
)
func main() {
fmt.Println("Running server")
fmt.Println("Config:", modlib.Config())
fmt.Println("Auth:", auth.GetAuth())
fmt.Println(serverlib.Hello())
}Attempting to import an internal package from outside the module results in an error such as:
use of internal package github.com/eliben/modlib/internal/auth not allowedPrivate packages are useful for encapsulating implementation details that should not be part of the public API, especially when adhering to semantic versioning.
Overall, a clear project layout helps developers quickly locate code, understand module boundaries, and maintain a clean separation between public and private components.
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.