Project Setup & Tooling
Go ships with everything you need to build, test, format, and manage dependencies. There are no build files, no package manager configs, and no IDE-specific project files. The go command is the build system, dependency manager, test runner, and formatter all in one.
Starting a Project
Every Go project starts with go mod init. This creates a go.mod file that declares your module path and Go version.
// Terminal: initialize a new module
// The module path is typically your repository URL
$ mkdir myservice && cd myservice
$ go mod init github.com/yourorg/myservice
go: creating new go.mod: module github.com/yourorg/myservice
This produces a go.mod file:
module github.com/yourorg/myservice
go 1.22
That is your entire project configuration. No pom.xml, no package.json, no Cargo.toml with 50 fields. Just the module path and the Go version.
go.mod & go.sum
go.mod
The go.mod file tracks your module name, Go version, and direct dependencies. When you import a third-party package and run go mod tidy, Go adds it here automatically.
module github.com/yourorg/myservice
go 1.22
require (
github.com/go-chi/chi/v5 v5.0.12
github.com/jackc/pgx/v5 v5.5.5
)
require (
// indirect dependencies are managed automatically
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201171823-440d19dc326d // indirect
)
go.sum
The go.sum file contains cryptographic hashes of every dependency. It ensures reproducible builds -- if someone tampers with a module, the hash mismatch will cause a build failure. You never edit this file manually. Commit it to version control.
Key Commands for Dependencies
$ go mod tidy # Add missing, remove unused dependencies
$ go mod download # Download dependencies to local cache
$ go mod vendor # Copy dependencies into vendor/ directory
$ go mod why <pkg> # Explain why a package is a dependency
$ go get <pkg>@latest # Add or update a dependency
$ go get <pkg>@v1.2.3 # Pin to a specific version
The Go Toolchain
go run
Compiles and runs a Go file in one step. Great for development, not for production.
$ go run main.go
$ go run . # Run the package in the current directory
$ go run ./cmd/server # Run a specific sub-command
go build
Compiles your code into a binary. The binary name defaults to the module or directory name.
$ go build -o myservice ./cmd/server
$ ls -la myservice
-rwxr-xr-x 1 user staff 8234567 myservice
Cross-compilation is built in. Set GOOS and GOARCH to build for any supported platform:
$ GOOS=linux GOARCH=amd64 go build -o myservice-linux ./cmd/server
$ GOOS=darwin GOARCH=arm64 go build -o myservice-mac ./cmd/server
$ GOOS=windows GOARCH=amd64 go build -o myservice.exe ./cmd/server
No special toolchain installation required. It just works.
go test
Runs tests. Test files are named *_test.go and live alongside the code they test.
$ go test ./... # Test all packages recursively
$ go test -v ./internal/auth # Verbose output for one package
$ go test -run TestLogin ./... # Run tests matching a pattern
$ go test -race ./... # Enable the race detector
$ go test -cover ./... # Show coverage percentage
go fmt
Formats your code according to Go's canonical style. Tabs for indentation, specific alignment rules, consistent spacing. There are no options. There are no debates.
$ gofmt -w . # Format all files in place
$ gofmt -d . # Show diffs without modifying
Most editors run gofmt on save. If yours does not, configure it to do so. Every Go file in every Go project looks the same, and that is the point.
go vet
Static analysis that catches common mistakes the compiler misses: unreachable code, suspicious format strings, struct tags with typos, copying mutexes.
$ go vet ./...
Run go vet as part of your CI pipeline. It catches real bugs.
Project Structure
Go does not enforce a project structure, but conventions have emerged. Here are the practical options.
Small Projects: Keep It Flat
For a CLI tool, small library, or simple service, a flat structure works fine:
myservice/
go.mod
go.sum
main.go
handler.go
handler_test.go
store.go
store_test.go
Do not create directories until you need them. Start flat, organize later.
Medium to Large Projects: cmd/ & internal/
When the project grows, the standard layout uses cmd/ for entry points and internal/ for private packages.
myservice/
go.mod
go.sum
cmd/
server/
main.go # Entry point for the API server
worker/
main.go # Entry point for the background worker
migrate/
main.go # Entry point for DB migrations
internal/
auth/
auth.go
auth_test.go
store/
postgres.go
postgres_test.go
handler/
routes.go
user.go
user_test.go
What These Directories Mean
cmd/ -- Each subdirectory is a separate binary. Each contains a main.go with package main. This is where func main() lives and nowhere else.
internal/ -- Packages here cannot be imported by code outside your module. The Go compiler enforces this. Use internal/ for code that is specific to your application and should not be treated as a public API.
pkg/ -- Some projects use pkg/ for packages intended to be imported by other projects. This convention is falling out of favor. If you are building a library, put your packages at the module root. If you are building an application, put them in internal/.
// cmd/server/main.go
package main
import (
"log"
"net/http"
"github.com/yourorg/myservice/internal/handler"
"github.com/yourorg/myservice/internal/store"
)
func main() {
db, err := store.Connect("postgres://localhost/myservice")
if err != nil {
log.Fatal(err)
}
defer db.Close()
router := handler.NewRouter(db)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", router))
}
go install for Tools
go install builds and installs a binary to $GOPATH/bin (or $GOBIN if set). Use it for installing Go-based development tools.
$ go install golang.org/x/tools/gopls@latest # Go language server
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
$ go install github.com/air-verse/air@latest # Live reload
These are system-wide tools, not project dependencies. They do not appear in your go.mod.
Build Tags & ldflags
Build Tags
Conditional compilation via build tags lets you include or exclude files based on OS, architecture, or custom tags.
//go:build linux
package myservice
// This file is only compiled on Linux
ldflags for Version Injection
Inject build-time variables (version, commit hash) without config files:
$ go build -ldflags "-X main.version=1.2.3 -X main.commit=$(git rev-parse --short HEAD)" -o myservice
package main
var (
version = "dev"
commit = "none"
)
func main() {
fmt.Printf("myservice %s (%s)\n", version, commit)
}
A Practical Workflow
Here is what a typical development loop looks like:
$ go mod init github.com/yourorg/myservice # Once, at project creation
$ go mod tidy # After adding/removing imports
$ go fmt ./... # Before every commit (or on save)
$ go vet ./... # Before every commit
$ go test ./... # Before every commit
$ go build -o myservice ./cmd/server # When you need a binary
$ go test -race -cover ./... # In CI
Common Pitfalls
- Creating complex directory structures for small projects. Start flat. Add
cmd/andinternal/when you actually have multiple binaries or need to hide packages. Over-structuring a 500-line project is waste. - Vendoring without a reason. Go modules with
go.sumalready guarantee reproducible builds. Vendoring is useful for air-gapped environments or when you want dependencies visible in code review, but it is not the default. - Forgetting go mod tidy. If you add an import and your build fails, run
go mod tidy. It resolves missing dependencies and removes unused ones. - Not running gofmt. If your team is debating formatting, you are doing it wrong. Run
gofmton save. The discussion is over. - Using GOPATH mode. GOPATH-based dependency management is legacy. Always use modules (
go mod init). If you are following a tutorial that mentions GOPATH without modules, find a newer tutorial.
Key Takeaways
go mod initcreates your project.go.modandgo.summanage dependencies. That is the entire build configuration.- The
gocommand handles building, testing, formatting, vetting, and dependency management. No third-party build tools required. - Start with a flat project structure. Graduate to
cmd/andinternal/when complexity demands it. gofmtenforces one canonical format. Run it on every save. There is nothing to configure.- Cross-compilation is built in: set
GOOSandGOARCH, thengo build. - Use
go installfor development tools, not for project dependencies.