| name | moai-lang-go |
| version | 2.0.0 |
| created | Thu Nov 06 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
| updated | Thu Nov 06 2025 00:00:00 GMT+0000 (Coordinated Universal Time) |
| status | active |
| description | Go best practices with modern cloud-native development, performance optimization, and concurrent programming for 2025 |
| keywords | go, golang, backend, microservices, cloud-native, performance, concurrency, devops |
| allowed-tools | Read, Write, Edit, Bash, WebFetch, WebSearch |
Go Development Mastery
Modern Go Development with 2025 Best Practices
Comprehensive Go development guidance covering cloud-native services, high-performance concurrent programming, microservices architecture, and production-ready applications using the latest tools and methodologies.
What It Does
- Cloud-Native Services: Kubernetes, Docker, and container-native applications
- High-Performance APIs: Fast HTTP servers with minimal memory footprint
- Concurrent Programming: Goroutines, channels, and advanced patterns
- Microservices Architecture: Service discovery, load balancing, distributed systems
- Testing & Quality: Comprehensive testing, benchmarking, and quality assurance
- Database Integration: SQL, NoSQL, caching, and data pipelines
- DevOps Integration: CI/CD, monitoring, observability, and deployment
- Enterprise Patterns: Clean architecture, domain-driven design, scalability
When to Use
Perfect Scenarios
- Building microservices and distributed systems
- High-performance network services and APIs
- Cloud-native applications and DevOps tools
- Concurrent and parallel processing applications
- System programming and infrastructure tools
- CLI applications and automation scripts
- Real-time data processing and streaming
Common Triggers
- "Create Go microservice"
- "Set up Go API server"
- "Go concurrent programming"
- "Go best practices"
- "Optimize Go performance"
- "Deploy Go application"
Tool Version Matrix (2025-11-06)
Core Go
- Go: 1.25.x (latest) / 1.23.x (LTS)
- Gin: 1.10.x - HTTP web framework
- Echo: 4.12.x - High-performance web framework
- Chi: 5.0.x - Lightweight router
- Fiber: 3.x - Express.js-inspired framework
Database & Storage
- GORM: 2.0.x - ORM for Go
- sqlx: 1.4.x - SQL extensions
- pgx: 5.6.x - PostgreSQL driver
- Redis: 9.x - Redis client
- MongoDB: 2.x - MongoDB driver
Testing Tools
- Testify: 1.9.x - Testing toolkit
- GoMock: 1.6.x - Mock generation
- gomock: Built-in mocking framework
- goleak: 1.3.x - Goroutine leak detection
Development Tools
- Air: 1.53.x - Live reload
- Gin-swagger: 1.6.x - API documentation
- Zap: 1.27.x - Structured logging
- Viper: 1.19.x - Configuration management
- golangci-lint: 1.62.x - Linter aggregator
Observability
- Prometheus: 4.x client - Metrics
- Jaeger: 2.x client - Distributed tracing
- OpenTelemetry: 1.30.x - Observability framework
- pprof: Built-in profiling
Ecosystem Overview
Project Setup (2025 Best Practice)
# Modern Go project with modules
go mod init github.com/your-org/your-project
# Initialize project structure
mkdir -p {cmd/server,internal/{handler,service,repository},pkg/{utils,middleware},configs,deployments/{docker,k8s},scripts,test}
# Install essential tools
go install github.com/cosmtrek/air@latest
go install github.com/swaggo/swag/cmd/swag@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install go.uber.org/mock/mockgen@latest
# Generate code documentation
swag init -g cmd/server/main.go
Modern Project Structure
my-go-project/
├── go.mod
├── go.sum
├── Makefile # Build commands and utilities
├── README.md
├── .github/
│ └── workflows/ # CI/CD pipelines
├── cmd/
│ └── server/
│ └── main.go # Application entry point
├── internal/ # Private application code
│ ├── handler/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access layer
│ ├── model/ # Domain models
│ └── middleware/ # HTTP middleware
├── pkg/ # Public library code
│ ├── utils/ # Utility functions
│ ├── config/ # Configuration
│ └── logger/ # Logging utilities
├── configs/ # Configuration files
├── deployments/
│ ├── docker/ # Docker configurations
│ └── k8s/ # Kubernetes manifests
├── scripts/ # Build and utility scripts
├── test/ # Integration and e2e tests
├── docs/ # API documentation
└── migrations/ # Database migrations
Modern Go Patterns
Context-First Design
// handler/user.go
package handler
import (
"context"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/your-org/your-project/internal/model"
"github.com/your-org/your-project/internal/service"
)
type UserHandler struct {
userService service.UserService
logger Logger
}
func NewUserHandler(userService service.UserService, logger Logger) *UserHandler {
return &UserHandler{
userService: userService,
logger: logger,
}
}
// CreateUser handles user creation requests
func (h *UserHandler) CreateUser(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
var req model.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("Invalid request body", "error", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user, err := h.userService.CreateUser(ctx, req)
if err != nil {
h.logger.Error("Failed to create user", "error", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
return
}
c.JSON(http.StatusCreated, user)
}
// GetUser retrieves a user by ID
func (h *UserHandler) GetUser(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
userID := c.Param("id")
user, err := h.userService.GetUser(ctx, userID)
if err != nil {
if err == service.ErrUserNotFound {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
h.logger.Error("Failed to get user", "userID", userID, "error", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal server error"})
return
}
c.JSON(http.StatusOK, user)
}
Service Layer with Error Handling
// service/user.go
package service
import (
"context"
"errors"
"fmt"
"github.com/your-org/your-project/internal/model"
"github.com/your-org/your-project/internal/repository"
)
var (
ErrUserNotFound = errors.New("user not found")
ErrUserExists = errors.New("user already exists")
ErrInvalidInput = errors.New("invalid input")
)
type UserService interface {
CreateUser(ctx context.Context, req model.CreateUserRequest) (*model.User, error)
GetUser(ctx context.Context, id string) (*model.User, error)
UpdateUser(ctx context.Context, id string, req model.UpdateUserRequest) (*model.User, error)
DeleteUser(ctx context.Context, id string) error
ListUsers(ctx context.Context, filter model.UserFilter) ([]*model.User, int, error)
}
type userService struct {
userRepo repository.UserRepository
logger Logger
}
func NewUserService(userRepo repository.UserRepository, logger Logger) UserService {
return &userService{
userRepo: userRepo,
logger: logger,
}
}
func (s *userService) CreateUser(ctx context.Context, req model.CreateUserRequest) (*model.User, error) {
// Validate input
if err := req.Validate(); err != nil {
s.logger.Warn("Invalid user creation request", "error", err)
return nil, fmt.Errorf("%w: %v", ErrInvalidInput, err)
}
// Check if user already exists
existing, err := s.userRepo.GetByEmail(ctx, req.Email)
if err == nil && existing != nil {
s.logger.Warn("User already exists", "email", req.Email)
return nil, ErrUserExists
}
// Create user
user := &model.User{
ID: generateID(),
Email: req.Email,
Name: req.Name,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.userRepo.Create(ctx, user); err != nil {
s.logger.Error("Failed to create user", "error", err)
return nil, fmt.Errorf("failed to create user: %w", err)
}
s.logger.Info("User created successfully", "userID", user.ID)
return user, nil
}
func (s *userService) GetUser(ctx context.Context, id string) (*model.User, error) {
user, err := s.userRepo.GetByID(ctx, id)
if err != nil {
if errors.Is(err, repository.ErrNotFound) {
return nil, ErrUserNotFound
}
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
Repository Pattern with SQLx
// repository/user.go
package repository
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // PostgreSQL driver
"github.com/your-org/your-project/internal/model"
)
var (
ErrNotFound = errors.New("record not found")
ErrDuplicate = errors.New("duplicate record")
)
type UserRepository interface {
Create(ctx context.Context, user *model.User) error
GetByID(ctx context.Context, id string) (*model.User, error)
GetByEmail(ctx context.Context, email string) (*model.User, error)
Update(ctx context.Context, user *model.User) error
Delete(ctx context.Context, id string) error
List(ctx context.Context, filter UserFilter) ([]*model.User, int, error)
}
type userRepository struct {
db *sqlx.DB
}
func NewUserRepository(db *sqlx.DB) UserRepository {
return &userRepository{db: db}
}
func (r *userRepository) Create(ctx context.Context, user *model.User) error {
query := `
INSERT INTO users (id, email, name, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (email) DO NOTHING
`
result, err := r.db.ExecContext(ctx, query, user.ID, user.Email, user.Name, user.CreatedAt, user.UpdatedAt)
if err != nil {
return fmt.Errorf("failed to insert user: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return ErrDuplicate
}
return nil
}
func (r *userRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
query := `
SELECT id, email, name, created_at, updated_at
FROM users
WHERE id = $1
`
var user model.User
err := r.db.GetContext(ctx, &user, query, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrNotFound
}
return nil, fmt.Errorf("failed to get user by ID: %w", err)
}
return &user, nil
}
func (r *userRepository) List(ctx context.Context, filter UserFilter) ([]*model.User, int, error) {
// Build dynamic query
baseQuery := `
SELECT id, email, name, created_at, updated_at
FROM users
WHERE 1=1
`
countQuery := `
SELECT COUNT(*)
FROM users
WHERE 1=1
`
var args []interface{}
conditions := []string{}
if filter.Email != "" {
conditions = append(conditions, "email = $"+fmt.Sprintf("%d", len(args)+1))
args = append(args, filter.Email)
}
if len(conditions) > 0 {
whereClause := " AND " + fmt.Sprintf(" AND ", conditions)
baseQuery += whereClause
countQuery += whereClause
}
// Get total count
var total int
err := r.db.GetContext(ctx, &total, countQuery, args...)
if err != nil {
return nil, 0, fmt.Errorf("failed to count users: %w", err)
}
// Add pagination
if filter.Limit > 0 {
baseQuery += fmt.Sprintf(" LIMIT $%d OFFSET $%d", len(args)+1, len(args)+2)
args = append(args, filter.Limit, filter.Offset)
}
var users []*model.User
err = r.db.SelectContext(ctx, &users, baseQuery, args...)
if err != nil {
return nil, 0, fmt.Errorf("failed to list users: %w", err)
}
return users, total, nil
}
Concurrent Programming
Worker Pool Pattern
// pkg/worker/pool.go
package worker
import (
"context"
"sync"
"time"
)
type Task[T any] interface {
Execute(ctx context.Context) (T, error)
}
type Result[T any] struct {
Value T
Error error
}
type WorkerPool[T any] struct {
workers int
taskQueue chan Task[T]
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
func NewWorkerPool[T any](workers int) *WorkerPool[T] {
ctx, cancel := context.WithCancel(context.Background())
return &WorkerPool[T]{
workers: workers,
taskQueue: make(chan Task[T], workers*2),
ctx: ctx,
cancel: cancel,
}
}
func (p *WorkerPool[T]) Start() {
for i := 0; i < p.workers; i++ {
p.wg.Add(1)
go p.worker(i)
}
}
func (p *WorkerPool[T]) worker(id int) {
defer p.wg.Done()
for {
select {
case task := <-p.taskQueue:
if task == nil {
return
}
result, err := task.Execute(p.ctx)
if err != nil {
// Handle error (could use error channel or logging)
continue
}
// Process result
case <-p.ctx.Done():
return
}
}
}
func (p *WorkerPool[T]) Submit(task Task[T]) {
select {
case p.taskQueue <- task:
case <-p.ctx.Done():
return
}
}
func (p *WorkerPool[T]) Stop() {
close(p.taskQueue)
p.cancel()
p.wg.Wait()
}
// Example usage
type ImageProcessingTask struct {
ImagePath string
}
func (t *ImageProcessingTask) Execute(ctx context.Context) (string, error) {
// Simulate image processing
time.Sleep(100 * time.Millisecond)
return "processed_" + t.ImagePath, nil
}
func ProcessImages(imagePaths []string) []string {
pool := NewWorkerPool[string](10)
pool.Start()
defer pool.Stop()
var results []string
var mu sync.Mutex
for _, path := range imagePaths {
task := &ImageProcessingTask{ImagePath: path}
go func(t *ImageProcessingTask) {
result, err := t.Execute(context.Background())
if err == nil {
mu.Lock()
results = append(results, result)
mu.Unlock()
}
}(task)
}
return results
}
Fan-In/Fan-Out Pattern
// pkg/concurrent/fan.go
package concurrent
import (
"context"
"sync"
)
// FanIn merges multiple input channels into a single output channel
func FanIn[T any](ctx context.Context, channels ...<-chan T) <-chan T {
var wg sync.WaitGroup
out := make(chan T)
output := func(c <-chan T) {
defer wg.Done()
for {
select {
case v, ok := <-c:
if !ok {
return
}
select {
case out <- v:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}
wg.Add(len(channels))
for _, c := range channels {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
// FanOut distributes input across multiple output channels
func FanOut[T any](ctx context.Context, in <-chan T, n int) []<-chan T {
outs := make([]chan T, n)
for i := 0; i < n; i++ {
outs[i] = make(chan T)
}
distribute := func() {
defer func() {
for _, out := range outs {
close(out)
}
}()
for {
select {
case v, ok := <-in:
if !ok {
return
}
for _, out := range outs {
select {
case out <- v:
case <-ctx.Done():
return
}
}
case <-ctx.Done():
return
}
}
}
go distribute()
return outs
}
// Pipeline combines fan-out and fan-in for parallel processing
func Pipeline[T, R any](
ctx context.Context,
input []T,
workers int,
process func(context.Context, T) (R, error),
) (<-chan R, <-chan error) {
out := make(chan R, workers)
errChan := make(chan error, workers)
// Create input channel
in := make(chan T, len(input))
for _, item := range input {
in <- item
}
close(in)
// Fan out to workers
channels := FanOut(ctx, in, workers)
// Process each item
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan T) {
defer wg.Done()
for item := range c {
result, err := process(ctx, item)
if err != nil {
select {
case errChan <- err:
case <-ctx.Done():
return
}
continue
}
select {
case out <- result:
case <-ctx.Done():
return
}
}
}(ch)
}
go func() {
wg.Wait()
close(out)
close(errChan)
}()
return out, errChan
}
Performance Optimization
Memory Pooling
// pkg/pool/buffer.go
package pool
import (
"sync"
)
var (
bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 0, 1024) // Pre-allocate 1KB
},
}
)
// GetBuffer returns a buffer from the pool
func GetBuffer() []byte {
return bufferPool.Get().([]byte)
}
// PutBuffer returns a buffer to the pool
func PutBuffer(buf []byte) {
if cap(buf) <= 64*1024 { // Don't pool large buffers
bufferPool.Put(buf[:0]) // Reset length but keep capacity
}
}
// Usage example
func ProcessData(data []byte) ([]byte, error) {
buf := GetBuffer()
defer PutBuffer(buf)
// Process data using the pooled buffer
buf = append(buf, data...)
// ... processing logic ...
result := make([]byte, len(buf))
copy(result, buf)
return result, nil
}
String Building Optimization
// pkg/utils/strings.go
package utils
import (
"strings"
"sync"
)
var stringBuilderPool = sync.Pool{
New: func() interface{} {
return &strings.Builder{}
},
}
// StringBuilderPool provides a thread-safe string builder pool
func GetStringBuilder() *strings.Builder {
sb := stringBuilderPool.Get().(*strings.Builder)
sb.Reset()
return sb
}
func PutStringBuilder(sb *strings.Builder) {
if sb.Cap() <= 4096 { // Don't pool large builders
stringBuilderPool.Put(sb)
}
}
// FastJoin efficiently joins strings using a pooled builder
func FastJoin(strs ...string) string {
if len(strs) == 0 {
return ""
}
if len(strs) == 1 {
return strs[0]
}
sb := GetStringBuilder()
defer PutStringBuilder(sb)
for _, s := range strs {
sb.WriteString(s)
}
return sb.String()
}
// Usage example
func BuildMessage(parts []string) string {
sb := GetStringBuilder()
defer PutStringBuilder(sb)
for i, part := range parts {
if i > 0 {
sb.WriteString(" ")
}
sb.WriteString(part)
}
return sb.String()
}
Testing Strategies
Comprehensive Unit Testing
// service/user_test.go
package service
import (
"context"
"errors"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/your-org/your-project/internal/model"
"github.com/your-org/your-project/internal/repository"
)
// MockUserRepository is a mock implementation of UserRepository
type MockUserRepository struct {
mock.Mock
}
func (m *MockUserRepository) Create(ctx context.Context, user *model.User) error {
args := m.Called(ctx, user)
return args.Error(0)
}
func (m *MockUserRepository) GetByID(ctx context.Context, id string) (*model.User, error) {
args := m.Called(ctx, id)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*model.User), args.Error(1)
}
func (m *MockUserRepository) GetByEmail(ctx context.Context, email string) (*model.User, error) {
args := m.Called(ctx, email)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*model.User), args.Error(1)
}
func TestUserService_CreateUser_Success(t *testing.T) {
// Arrange
mockRepo := new(MockUserRepository)
mockLogger := &MockLogger{} // Assume you have this
service := NewUserService(mockRepo, mockLogger)
req := model.CreateUserRequest{
Email: "test@example.com",
Name: "Test User",
}
expectedUser := &model.User{
ID: "user-123",
Email: req.Email,
Name: req.Name,
}
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(nil, repository.ErrNotFound)
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*model.User")).Return(nil)
// Act
ctx := context.Background()
user, err := service.CreateUser(ctx, req)
// Assert
require.NoError(t, err)
assert.NotNil(t, user)
assert.Equal(t, req.Email, user.Email)
assert.Equal(t, req.Name, user.Name)
mockRepo.AssertExpectations(t)
}
func TestUserService_CreateUser_EmailExists(t *testing.T) {
// Arrange
mockRepo := new(MockUserRepository)
mockLogger := &MockLogger{}
service := NewUserService(mockRepo, mockLogger)
req := model.CreateUserRequest{
Email: "existing@example.com",
Name: "Test User",
}
existingUser := &model.User{
ID: "existing-123",
Email: req.Email,
}
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(existingUser, nil)
// Act
ctx := context.Background()
user, err := service.CreateUser(ctx, req)
// Assert
require.Error(t, err)
assert.Nil(t, user)
assert.Equal(t, ErrUserExists, err)
mockRepo.AssertExpectations(t)
}
func TestUserService_GetUser_NotFound(t *testing.T) {
// Arrange
mockRepo := new(MockUserRepository)
mockLogger := &MockLogger{}
service := NewUserService(mockRepo, mockLogger)
userID := "nonexistent"
mockRepo.On("GetByID", mock.Anything, userID).Return(nil, repository.ErrNotFound)
// Act
ctx := context.Background()
user, err := service.GetUser(ctx, userID)
// Assert
require.Error(t, err)
assert.Nil(t, user)
assert.Equal(t, ErrUserNotFound, err)
mockRepo.AssertExpectations(t)
}
// Benchmark example
func BenchmarkUserService_CreateUser(b *testing.B) {
mockRepo := new(MockUserRepository)
mockLogger := &MockLogger{}
service := NewUserService(mockRepo, mockLogger)
req := model.CreateUserRequest{
Email: "test@example.com",
Name: "Test User",
}
mockRepo.On("GetByEmail", mock.Anything, req.Email).Return(nil, repository.ErrNotFound)
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*model.User")).Return(nil)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := service.CreateUser(ctx, req)
if err != nil {
b.Fatal(err)
}
}
}
Integration Testing with Testcontainers
// test/integration/user_test.go
package integration
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/your-org/your-project/internal/model"
"github.com/your-org/your-project/internal/repository"
"github.com/your-org/your-project/internal/service"
"github.com/your-org/your-project/pkg/config"
)
func TestUserService_Integration(t *testing.T) {
// Setup test database container
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "postgres:16",
ExposedPorts: []string{"5432/tcp"},
Env: map[string]string{
"POSTGRES_DB": "testdb",
"POSTGRES_USER": "test",
"POSTGRES_PASSWORD": "test",
},
WaitingFor: wait.ForLog("database system is ready to accept connections"),
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
})
require.NoError(t, err)
defer container.Terminate(ctx)
// Get database connection info
host, err := container.Host(ctx)
require.NoError(t, err)
port, err := container.MappedPort(ctx, "5432")
require.NoError(t, err)
// Connect to database
dbConfig := config.Database{
Host: host,
Port: port.Int(),
User: "test",
Password: "test",
DBName: "testdb",
SSLMode: "disable",
}
db, err := setupTestDB(dbConfig)
require.NoError(t, err)
defer db.Close()
// Run migrations
err = runMigrations(db)
require.NoError(t, err)
// Setup service
userRepo := repository.NewUserRepository(db)
userService := service.NewUserService(userRepo, &MockLogger{})
// Test user creation
t.Run("Create and Get User", func(t *testing.T) {
req := model.CreateUserRequest{
Email: "integration@example.com",
Name: "Integration Test User",
}
// Create user
user, err := userService.CreateUser(ctx, req)
require.NoError(t, err)
assert.NotEmpty(t, user.ID)
assert.Equal(t, req.Email, user.Email)
assert.Equal(t, req.Name, user.Name)
assert.False(t, user.CreatedAt.IsZero())
// Get user
retrievedUser, err := userService.GetUser(ctx, user.ID)
require.NoError(t, err)
assert.Equal(t, user.ID, retrievedUser.ID)
assert.Equal(t, user.Email, retrievedUser.Email)
assert.Equal(t, user.Name, retrievedUser.Name)
})
t.Run("List Users", func(t *testing.T) {
// Create multiple users
for i := 0; i < 5; i++ {
req := model.CreateUserRequest{
Email: fmt.Sprintf("user%d@example.com", i),
Name: fmt.Sprintf("User %d", i),
}
_, err := userService.CreateUser(ctx, req)
require.NoError(t, err)
}
// List users
users, total, err := userService.ListUsers(ctx, model.UserFilter{
Limit: 10,
Offset: 0,
})
require.NoError(t, err)
assert.Equal(t, 6, total) // 5 new users + 1 from previous test
assert.Equal(t, 6, len(users))
})
}
// Helper functions
func setupTestDB(cfg config.Database) (*sqlx.DB, error) {
dsn := fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode,
)
db, err := sqlx.Connect(context.Background(), "postgres", dsn)
if err != nil {
return nil, err
}
// Wait for database to be ready
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
for {
err := db.PingContext(ctx)
if err == nil {
break
}
select {
case <-time.After(1 * time.Second):
continue
case <-ctx.Done():
return nil, ctx.Err()
}
}
return db, nil
}
Security Best Practices
Input Validation and Sanitization
// pkg/validation/validator.go
package validation
import (
"regexp"
"strings"
"unicode"
"github.com/go-playground/validator/v10"
)
var (
validate = validator.New()
// Email regex pattern
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
// Strong password regex
passwordRegex = regexp.MustCompile(`^.{8,}$`) // At least 8 characters
)
// ValidationResult represents validation result
type ValidationResult struct {
Valid bool
Errors map[string]string
}
// New creates a new validator
func New() *Validator {
return &Validator{}
}
// Validator handles input validation
type Validator struct{}
// ValidateStruct validates a struct using validator tags
func (v *Validator) ValidateStruct(s interface{}) ValidationResult {
err := validate.Struct(s)
if err == nil {
return ValidationResult{Valid: true}
}
errors := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
field := err.Field()
switch err.Tag() {
case "required":
errors[field] = field + " is required"
case "email":
errors[field] = field + " must be a valid email address"
case "min":
errors[field] = field + " must be at least " + err.Param() + " characters"
case "max":
errors[field] = field + " must be at most " + err.Param() + " characters"
default:
errors[field] = field + " is invalid"
}
}
return ValidationResult{Valid: false, Errors: errors}
}
// ValidateEmail validates email format
func (v *Validator) ValidateEmail(email string) bool {
email = strings.TrimSpace(strings.ToLower(email))
return emailRegex.MatchString(email) && len(email) <= 254
}
// ValidatePassword validates password strength
func (v *Validator) ValidatePassword(password string) ValidationResult {
errors := make(map[string]string)
if len(password) < 8 {
errors["length"] = "Password must be at least 8 characters long"
}
if !containsUppercase(password) {
errors["uppercase"] = "Password must contain at least one uppercase letter"
}
if !containsLowercase(password) {
errors["lowercase"] = "Password must contain at least one lowercase letter"
}
if !containsDigit(password) {
errors["digit"] = "Password must contain at least one digit"
}
if !containsSpecialChar(password) {
errors["special"] = "Password must contain at least one special character"
}
return ValidationResult{
Valid: len(errors) == 0,
Errors: errors,
}
}
// SanitizeInput sanitizes user input
func (v *Validator) SanitizeInput(input string) string {
// Remove potentially dangerous characters
sanitized := strings.ReplaceAll(input, "<", "<")
sanitized = strings.ReplaceAll(sanitized, ">", ">")
sanitized = strings.ReplaceAll(sanitized, "&", "&")
sanitized = strings.ReplaceAll(sanitized, "\"", """)
sanitized = strings.ReplaceAll(sanitized, "'", "'")
// Trim whitespace
return strings.TrimSpace(sanitized)
}
// Helper functions
func containsUppercase(s string) bool {
for _, r := range s {
if unicode.IsUpper(r) {
return true
}
}
return false
}
func containsLowercase(s string) bool {
for _, r := range s {
if unicode.IsLower(r) {
return true
}
}
return false
}
func containsDigit(s string) bool {
for _, r := range s {
if unicode.IsDigit(r) {
return true
}
}
return false
}
func containsSpecialChar(s string) bool {
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
return true
}
}
return false
}
Authentication and Authorization
// pkg/auth/jwt.go
package auth
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// Claims represents JWT claims
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// JWTManager handles JWT token operations
type JWTManager struct {
secretKey []byte
expiration time.Duration
}
// NewJWTManager creates a new JWT manager
func NewJWTManager(secretKey string, expiration time.Duration) *JWTManager {
return &JWTManager{
secretKey: []byte(secretKey),
expiration: expiration,
}
}
// GenerateToken generates a new JWT token
func (m *JWTManager) GenerateToken(userID, email, role string) (string, error) {
claims := &Claims{
UserID: userID,
Email: email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.expiration)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "your-app",
Subject: userID,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(m.secretKey)
}
// ValidateToken validates and parses a JWT token
func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return m.secretKey, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}
// RefreshToken generates a new token with extended expiration
func (m *JWTManager) RefreshToken(claims *Claims) (string, error) {
claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(m.expiration))
claims.IssuedAt = jwt.NewNumericDate(time.Now())
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(m.secretKey)
}
// Middleware for authentication
func (m *JWTManager) AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Authorization header required", http.StatusUnauthorized)
return
}
tokenString := strings.Replace(authHeader, "Bearer ", "", 1)
claims, err := m.ValidateToken(tokenString)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Add claims to context
ctx := context.WithValue(r.Context(), "user_claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Role-based authorization middleware
func (m *JWTManager) RequireRole(requiredRole string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims, ok := r.Context().Value("user_claims").(*Claims)
if !ok {
http.Error(w, "User claims not found", http.StatusUnauthorized)
return
}
if claims.Role != requiredRole {
http.Error(w, "Insufficient permissions", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
Integration Patterns
gRPC Service Implementation
// api/proto/user.proto
syntax = "proto3";
package user.v1;
option go_package = "github.com/your-org/your-project/api/user/v1;userv1";
service UserService {
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}
message User {
string id = 1;
string email = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
}
message CreateUserRequest {
string email = 1;
string name = 2;
}
message CreateUserResponse {
User user = 1;
}
// server/grpc/user_service.go
package grpc
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/your-org/your-project/api/user/v1"
"github.com/your-org/your-project/internal/service"
)
type GRPCUserServer struct {
userv1.UnimplementedUserServiceServer
userService service.UserService
}
func NewGRPCUserServer(userService service.UserService) *GRPCUserServer {
return &GRPCUserServer{
userService: userService,
}
}
func (s *GRPCUserServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
// Convert protobuf to domain model
createReq := model.CreateUserRequest{
Email: req.GetEmail(),
Name: req.GetName(),
}
// Call service layer
user, err := s.userService.CreateUser(ctx, createReq)
if err != nil {
if errors.Is(err, service.ErrUserExists) {
return nil, status.Error(codes.AlreadyExists, "user already exists")
}
if errors.Is(err, service.ErrInvalidInput) {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Error(codes.Internal, "internal server error")
}
// Convert domain model to protobuf
return &userv1.CreateUserResponse{
User: s.userToProto(user),
}, nil
}
func (s *GRPCUserServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
user, err := s.userService.GetUser(ctx, req.GetId())
if err != nil {
if errors.Is(err, service.ErrUserNotFound) {
return nil, status.Error(codes.NotFound, "user not found")
}
return nil, status.Error(codes.Internal, "internal server error")
}
return &userv1.GetUserResponse{
User: s.userToProto(user),
}, nil
}
func (s *GRPCUserServer) userToProto(user *model.User) *userv1.User {
return &userv1.User{
Id: user.ID,
Email: user.Email,
Name: user.Name,
CreatedAt: timestamppb.New(user.CreatedAt),
UpdatedAt: timestamppb.New(user.UpdatedAt),
}
}
Message Queue Integration
// pkg/messaging/nats.go
package messaging
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/nats-io/nats.go"
)
// Message represents a message payload
type Message struct {
ID string `json:"id"`
Type string `json:"type"`
Data map[string]interface{} `json:"data"`
Timestamp time.Time `json:"timestamp"`
}
// MessageHandler handles incoming messages
type MessageHandler func(ctx context.Context, msg *Message) error
// MessageBroker defines message broker interface
type MessageBroker interface {
Publish(ctx context.Context, subject string, msg *Message) error
Subscribe(ctx context.Context, subject string, handler MessageHandler) error
Close() error
}
// NATSMessageBroker implements MessageBroker using NATS
type NATSMessageBroker struct {
conn *nats.Conn
js nats.JetStreamContext
}
func NewNATSMessageBroker(url string) (*NATSMessageBroker, error) {
conn, err := nats.Connect(url,
nats.ReconnectWait(2*time.Second),
nats.MaxReconnects(5),
nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
fmt.Printf("NATS disconnected: %v\n", err)
}),
nats.ReconnectHandler(func(nc *nats.Conn) {
fmt.Printf("NATS reconnected to %v\n", nc.ConnectedUrl())
}),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to NATS: %w", err)
}
js, err := conn.JetStream()
if err != nil {
return nil, fmt.Errorf("failed to get JetStream context: %w", err)
}
return &NATSMessageBroker{
conn: conn,
js: js,
}, nil
}
func (b *NATSMessageBroker) Publish(ctx context.Context, subject string, msg *Message) error {
data, err := json.Marshal(msg)
if err != nil {
return fmt.Errorf("failed to marshal message: %w", err)
}
// Use JetStream for persistent messaging
_, err = b.js.Publish(ctx, subject, data)
if err != nil {
return fmt.Errorf("failed to publish message: %w", err)
}
return nil
}
func (b *NATSMessageBroker) Subscribe(ctx context.Context, subject string, handler MessageHandler) error {
// Create stream if it doesn't exist
streamConfig := &nats.StreamConfig{
Name: "events",
Subjects: []string{subject},
Retention: nats.WorkQueuePolicy,
MaxBytes: 1024 * 1024 * 1024, // 1GB
Storage: nats.FileStorage,
Replicas: 1,
}
_, err := b.js.AddStream(streamConfig)
if err != nil && !errors.Is(err, nats.ErrStreamNameAlreadyInUse) {
return fmt.Errorf("failed to create stream: %w", err)
}
// Create consumer
consumerConfig := &nats.ConsumerConfig{
Durable: "worker",
AckPolicy: nats.AckExplicitPolicy,
}
_, err = b.js.AddConsumer("events", consumerConfig)
if err != nil && !errors.Is(err, nats.ErrConsumerAlreadyExists) {
return fmt.Errorf("failed to create consumer: %w", err)
}
// Subscribe to messages
_, err = b.js.Subscribe(subject, func(msg *nats.Msg) {
var message Message
if err := json.Unmarshal(msg.Data, &message); err != nil {
fmt.Printf("Failed to unmarshal message: %v\n", err)
msg.Nak()
return
}
if err := handler(ctx, &message); err != nil {
fmt.Printf("Handler failed: %v\n", err)
msg.Nak()
return
}
msg.Ack()
}, nats.Durable("worker"))
if err != nil {
return fmt.Errorf("failed to subscribe: %w", err)
}
return nil
}
func (b *NATSMessageBroker) Close() error {
b.conn.Close()
return nil
}
// Usage example
func setupEventHandlers(broker MessageBroker) {
// User created event handler
broker.Subscribe(context.Background(), "user.created", func(ctx context.Context, msg *Message) error {
fmt.Printf("User created: %v\n", msg)
// Send welcome email, update analytics, etc.
return nil
})
// User updated event handler
broker.Subscribe(context.Background(), "user.updated", func(ctx context.Context, msg *Message) error {
fmt.Printf("User updated: %v\n", msg)
// Update search index, notify other services, etc.
return nil
})
}
Modern Development Workflow
Configuration Management
// pkg/config/config.go
package config
import (
"fmt"
"os"
"strconv"
"time"
"github.com/spf13/viper"
)
// Config holds application configuration
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Auth AuthConfig `mapstructure:"auth"`
Logging LoggingConfig `mapstructure:"logging"`
Monitoring MonitoringConfig `mapstructure:"monitoring"`
}
// ServerConfig holds server configuration
type ServerConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
ReadTimeout time.Duration `mapstructure:"read_timeout"`
WriteTimeout time.Duration `mapstructure:"write_timeout"`
GracefulShutdownTimeout time.Duration `mapstructure:"graceful_shutdown_timeout"`
}
// DatabaseConfig holds database configuration
type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
DBName string `mapstructure:"db_name"`
SSLMode string `mapstructure:"ssl_mode"`
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
}
// Load loads configuration from file and environment variables
func Load() (*Config, error) {
// Set default values
setDefaults()
// Load from config file
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")
viper.AddConfigPath(".")
// Load environment variables
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// Config file not found is OK, we'll use defaults and env vars
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
}
// Validate configuration
if err := validate(&config); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
return &config, nil
}
func setDefaults() {
viper.SetDefault("server.host", "0.0.0.0")
viper.SetDefault("server.port", 8080)
viper.SetDefault("server.read_timeout", 30*time.Second)
viper.SetDefault("server.write_timeout", 30*time.Second)
viper.SetDefault("server.graceful_shutdown_timeout", 30*time.Second)
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.port", 5432)
viper.SetDefault("database.ssl_mode", "disable")
viper.SetDefault("database.max_open_conns", 25)
viper.SetDefault("database.max_idle_conns", 25)
viper.SetDefault("database.conn_max_lifetime", 5*time.Minute)
viper.SetDefault("redis.host", "localhost")
viper.SetDefault("redis.port", 6379)
viper.SetDefault("redis.db", 0)
viper.SetDefault("auth.jwt.expiration", 24*time.Hour)
viper.SetDefault("logging.level", "info")
viper.SetDefault("logging.format", "json")
}
func validate(config *Config) error {
if config.Server.Port <= 0 || config.Server.Port > 65535 {
return fmt.Errorf("invalid server port: %d", config.Server.Port)
}
if config.Database.Host == "" {
return fmt.Errorf("database host is required")
}
if config.Database.User == "" {
return fmt.Errorf("database user is required")
}
return nil
}
// GetDSN returns database connection string
func (c *DatabaseConfig) GetDSN() string {
return fmt.Sprintf(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
c.Host, c.Port, c.User, c.Password, c.DBName, c.SSLMode,
)
}
// GetRedisAddr returns Redis connection address
func (c *RedisConfig) GetAddr() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
// GetServerAddr returns server address
func (c *ServerConfig) GetAddr() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
Makefile for Development
# Makefile
.PHONY: help build run test clean lint format docker-build docker-run
# Variables
APP_NAME := your-app
VERSION := $(shell git describe --tags --always --dirty)
BUILD_TIME := $(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"
# Docker variables
DOCKER_REGISTRY := your-registry
DOCKER_TAG := $(DOCKER_REGISTRY)/$(APP_NAME):$(VERSION)
# Help
help:
@echo "Available commands:"
@echo " build Build the application"
@echo " run Run the application"
@echo " test Run tests"
@echo " test-coverage Run tests with coverage"
@echo " lint Run linter"
@echo " format Format code"
@echo " clean Clean build artifacts"
@echo " deps Install dependencies"
@echo " migrate Run database migrations"
@echo " docker-build Build Docker image"
@echo " docker-run Run Docker container"
@echo " generate Generate code (mocks, protobuf, etc.)"
# Build
build:
go build $(LDFLAGS) -o bin/$(APP_NAME) cmd/server/main.go
# Run
run:
go run $(LDFLAGS) cmd/server/main.go
# Development mode with hot reload
dev:
air
# Tests
test:
go test -v ./...
test-coverage:
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
test-bench:
go test -bench=. -benchmem ./...
# Linting
lint:
golangci-lint run
# Formatting
format:
go fmt ./...
goimports -w .
# Dependencies
deps:
go mod download
go mod tidy
# Generate code
generate:
go generate ./...
mockgen -source=internal/service/user.go -destination=test/mocks/user_service_mock.go
protoc --go_out=. --go-grpc_out=. api/proto/user.proto
# Database
migrate-up:
migrate -path migrations -database "$(shell grep -A5 'database:' configs/config.yaml | tail -n1 | cut -d' ' -f2)" up
migrate-down:
migrate -path migrations -database "$(shell grep -A5 'database:' configs/config.yaml | tail -n1 | cut -d' ' -f2)" down
# Clean
clean:
rm -rf bin/
rm -f coverage.out coverage.html
# Docker
docker-build:
docker build -t $(DOCKER_TAG) .
docker-run:
docker run -p 8080:8080 $(DOCKER_TAG)
docker-push:
docker push $(DOCKER_TAG)
# Install tools
install-tools:
go install github.com/cosmtrek/air@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install golang.org/x/tools/cmd/goimports@latest
go install github.com/golang/mock/mockgen@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Created by: MoAI Language Skill Factory
Last Updated: 2025-11-06
Version: 2.0.0
Go Target: 1.25+ with latest language features
This skill provides comprehensive Go development guidance with 2025 best practices, covering everything from basic concurrent programming to advanced cloud-native patterns and enterprise-grade applications.