Skip to main content

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

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