Modern Go Tooling: Tools Every Go Developer Should Know
Go ships with excellent built-in tooling (go fmt, go vet, go test, go build). The ecosystem has also developed strong third-party tools that fill gaps. This guide covers the tools that experienced Go developers actually use day-to-day.
golangci-lint: The Standard Linter
golangci-lint runs dozens of linters simultaneously, cached and in parallel:
# Install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# or: brew install golangci-lint
# Run
golangci-lint run
# Run on specific directory
golangci-lint run ./internal/...
# Auto-fix where possible
golangci-lint run --fix
Configure with .golangci.yml:
linters:
enable:
- errcheck # Check error returns are handled
- gosimple # Simplify code suggestions
- govet # Report suspicious constructs
- ineffassign # Detect ineffective assignments
- staticcheck # Advanced static analysis
- unused # Find unused code
- gofmt # Check formatting
- goimports # Check imports
- misspell # Fix common spelling mistakes
- revive # Fast, configurable, extensible linter
- exhaustive # Check switch exhaustiveness
- noctx # Detect http requests without context
linters-settings:
goimports:
local-prefixes: github.com/your-org
issues:
exclude-rules:
- path: _test\.go
linters:
- gosec
run:
timeout: 5m
Add to CI (GitHub Actions):
- uses: golangci/golangci-lint-action@v4
with:
version: latest
air: Live Reload for Development
air restarts your application automatically on code changes:
go install github.com/air-verse/air@latest
Initialize config:
air init # Creates .air.toml
.air.toml:
[build]
cmd = "go build -o ./tmp/main ./cmd/server"
bin = "./tmp/main"
delay = 500
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["assets", "tmp", "vendor"]
[screen]
clear_on_rebuild = true
air # Start with live reload
air handles build errors gracefully — shows the error without crashing, and picks up when you fix it.
sqlc: Type-Safe SQL
sqlc generates Go code from SQL queries. Write SQL, get fully typed Go functions:
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
sqlc.yaml:
version: "2"
sql:
- engine: "postgresql"
queries: "./db/queries/"
schema: "./db/schema/"
gen:
go:
package: "db"
out: "./internal/db"
emit_json_tags: true
emit_interface: true
Write your schema:
-- db/schema/users.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Write your queries:
-- db/queries/users.sql
-- name: GetUser :one
SELECT * FROM users WHERE id = $1;
-- name: ListUsers :many
SELECT * FROM users ORDER BY created_at DESC;
-- name: CreateUser :one
INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *;
-- name: UpdateUser :one
UPDATE users SET name = $2 WHERE id = $1 RETURNING *;
-- name: DeleteUser :exec
DELETE FROM users WHERE id = $1;
Generate:
sqlc generate
Use the generated code:
queries := db.New(conn)
// Fully typed — returns db.User
user, err := queries.GetUser(ctx, 42)
if err != nil {
return err
}
// CreateUser parameters are typed too
newUser, err := queries.CreateUser(ctx, db.CreateUserParams{
Email: "[email protected]",
Name: "Alice",
})
No manual SQL parsing, no reflection — just type-safe database access.
buf: Protocol Buffers Tooling
buf is the modern toolchain for protobuf development:
# Install
go install github.com/bufbuild/buf/cmd/buf@latest
# Initialize
buf config init
buf.yaml:
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
breaking:
use:
- FILE
# Lint protobuf files
buf lint
# Check for breaking changes
buf breaking --against '.git#branch=main'
# Generate code
buf generate
# Format proto files
buf format --write
buf.gen.yaml:
version: v2
plugins:
- remote: buf.build/protocolbuffers/go
out: gen/go
opt:
- paths=source_relative
- remote: buf.build/grpc/go
out: gen/go
opt:
- paths=source_relative
buf's built-in module registry means you don't need to vendor or manually manage protobuf dependencies.
Task: Modern Makefile Alternative
Task is a task runner with a cleaner syntax than Make:
go install github.com/go-task/task/v3/cmd/task@latest
Taskfile.yml:
version: '3'
tasks:
build:
desc: Build the application
cmds:
- go build -o ./bin/server ./cmd/server
test:
desc: Run tests
cmds:
- go test ./... -race -count=1
lint:
desc: Run linters
cmds:
- golangci-lint run
generate:
desc: Run code generation
cmds:
- sqlc generate
- buf generate
- go generate ./...
dev:
desc: Start dev server with live reload
cmds:
- air
ci:
desc: Full CI pipeline
deps: [generate, lint, test, build]
task build
task test
task ci
gofumpt: Stricter Formatting
gofumpt is a superset of gofmt with additional formatting rules:
go install mvdan.cc/gofumpt@latest
gofumpt -l -w .
Configure golangci-lint to use gofumpt instead of gofmt:
linters-settings:
gofumpt:
extra-rules: true
delve: Go Debugger
delve is the Go debugger. Most IDEs (GoLand, VS Code with Go extension) use it under the hood:
go install github.com/go-delve/delve/cmd/dlv@latest
# Debug a binary
dlv debug ./cmd/server
# Attach to running process
dlv attach <pid>
# Debug a test
dlv test ./internal/service/ -- -run TestMyTest
In VS Code, the Go extension uses delve automatically via the debug launch configuration.
govulncheck: Vulnerability Scanning
go install golang.org/x/vuln/cmd/govulncheck@latest
# Check for known vulnerabilities in dependencies
govulncheck ./...
Integrate into CI:
- run: govulncheck ./...
gops: Go Process Inspector
go install github.com/google/gops@latest
# List Go processes
gops
# Print goroutine dump
gops stack <pid>
# Print memory stats
gops memstats <pid>
Useful for debugging hanging goroutines or memory issues in production.
Recommended Project Scaffold
A starting point that incorporates these tools:
project/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── db/ ← sqlc generated
│ └── service/
├── proto/ ← protobuf definitions
├── gen/ ← buf generated
├── db/
│ ├── queries/ ← sqlc input
│ └── schema/ ← sqlc input
├── .air.toml
├── .golangci.yml
├── buf.yaml
├── buf.gen.yaml
├── sqlc.yaml
└── Taskfile.yml
Running task ci runs linting, testing, and building — CI and local dev use the same commands.