Pro Tips — Module and Package Management
Monorepo vs Multi-module
Monorepo (Single Repository, Multiple Modules)
company/
├── go.work ← Go Workspaces (Go 1.18+)
├── backend/
│ ├── go.mod (module github.com/company/backend)
│ └── main.go
├── shared/
│ ├── go.mod (module github.com/company/shared)
│ └── types.go
└── tools/
├── go.mod (module github.com/company/tools)
└── generate.go
Go Workspaces(go.work) for developing multiple modules locally together:
// go.work
go 1.21
use (
./backend
./shared
./tools
)
replace github.com/company/shared => ./shared
# Initialize workspace
go work init ./backend ./shared ./tools
# Add module to workspace
go work use ./newservice
# Sync workspace
go work sync
Which Structure to Choose?
Monorepo advantages:
- Easy code reuse
- Consistent dependency versions
- Atomic commits
Multi-module (polyrepo) advantages:
- Independent deployment cycles
- Clear team boundaries
- Simpler access control
Recommendations:
- Small teams → monorepo
- Microservices → multi-module
- Shared libraries present → use go.work
Popular Third-Party Library Ecosystem
Web Frameworks
// Gin — most popular web framework
// go get github.com/gin-gonic/gin
import "github.com/gin-gonic/gin"
r := gin.Default()
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
// Echo — high-performance web framework
// go get github.com/labstack/echo/v4
import "github.com/labstack/echo/v4"
// Chi — lightweight router (standard library compatible)
// go get github.com/go-chi/chi/v5
import "github.com/go-chi/chi/v5"
Databases
// GORM — Go ORM
// go get gorm.io/gorm
// go get gorm.io/driver/postgres
// sqlc — SQL → Go code generation
// go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest
// sqlx — database/sql extension
// go get github.com/jmoiron/sqlx
// migrate — DB migrations
// go get -tags 'postgres' github.com/golang-migrate/migrate/v4
Configuration Management
// Viper — unified config files, env vars, and flags
// go get github.com/spf13/viper
import "github.com/spf13/viper"
func initConfig() {
viper.SetConfigName("config") // File name (no extension)
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME/.config")
viper.AutomaticEnv() // Auto-bind env vars
viper.SetEnvPrefix("APP")
if err := viper.ReadInConfig(); err != nil {
// No file found — use env vars only
}
host := viper.GetString("database.host")
port := viper.GetInt("database.port")
_, _ = host, port
}
Logging
// zap — high-performance structured logging (by Uber)
// go get go.uber.org/zap
import "go.uber.org/zap"
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("Server starting",
zap.String("host", "localhost"),
zap.Int("port", 8080),
)
// logrus — structured logging
// go get github.com/sirupsen/logrus
// slog — Go 1.21 standard library (no external dependency needed)
import "log/slog"
slog.Info("Server starting", "host", "localhost", "port", 8080)
Testing
// testify — assert, mock, suite
// go get github.com/stretchr/testify
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAddUser(t *testing.T) {
user := addUser("Alice")
assert.NotNil(t, user)
assert.Equal(t, "Alice", user.Name)
require.NoError(t, user.Validate()) // Fails immediately if error
}
// gomock — interface mocking
// go install github.com/golang/mock/mockgen@latest
// testcontainers-go — real DB testing
// go get github.com/testcontainers/testcontainers-go
Dependency Vulnerability Scanning
# govulncheck — Go official vulnerability scanner
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
# Nancy — dependency vulnerability check
go install github.com/sonatype-nexus-community/nancy@latest
go list -json -deps ./... | nancy sleuth
Efficient go mod Management
# View current dependency tree
go mod graph | head -20
# Find which dependency uses a specific package
go mod why github.com/pkg/errors
# Check available versions
go list -m -versions github.com/gin-gonic/gin
# Check latest available versions for all dependencies
go list -m -u all
# Clean module cache
go clean -modcache
Build Tags
Compile different code based on build conditions.
//go:build linux && amd64
package platform
const OSName = "Linux AMD64"
//go:build !production
package config
// Development-only settings
var DebugMode = true
var LogLevel = "debug"
# Specify build tags
go build -tags production ./...
go test -tags integration ./...
Automating Workflows with Makefile
.PHONY: build test lint clean tidy
# Build
build:
go build -ldflags="-w -s" -o bin/server ./cmd/server
# Test with race detector
test:
go test -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# Linter
lint:
golangci-lint run ./...
# Clean up dependencies
tidy:
go mod tidy
go mod verify
# Vulnerability scan
security:
govulncheck ./...
# Run all
all: tidy lint test build
Package Publishing Checklist
Things to check before publishing your Go package.
□ Does the package name clearly reflect its purpose?
□ Do all exported APIs have GoDoc comments?
□ Does README.md include usage examples?
□ Is the module path in go.mod correct?
□ Is there a LICENSE file?
□ Is test coverage sufficient? (70%+ recommended)
□ Are example functions written in Example_xxx format?
□ Is there a CHANGELOG.md?
□ Is CI/CD configured? (GitHub Actions, etc.)
□ Does documentation render correctly on pkg.go.dev?
Standard Library First Principle
// ✅ When the standard library is sufficient
import (
"encoding/json" // JSON processing
"net/http" // HTTP server/client
"log/slog" // Structured logging (Go 1.21+)
"testing" // Basic testing
"sync" // Basic concurrency tools
)
// Questions to ask before adding an external library:
// 1. Can the standard library solve this?
// 2. Is the library actively maintained?
// 3. Are there security or license concerns?
// 4. Is the build time and binary size increase acceptable?
Key Takeaways
- Go Workspaces: Use for developing multiple modules locally in a monorepo
- Standard library first: Only add external dependencies when truly necessary
- govulncheck: Regularly scan dependencies for security vulnerabilities
- go mod why: Trace why a dependency was introduced
- Build tags: Branch code by environment (dev/prod/test)
- Makefile: Automate repetitive build/test workflows