Alpine Linux is the perfect platform for Go applications due to its minimal footprint and security features. This guide will help you set up a complete Go development and deployment environment, from basic web servers to production-ready microservices.
Table of Contents
- Prerequisites
- Installing Go on Alpine
- Development Environment Setup
- Creating a Basic Web Application
- Database Integration
- RESTful API Development
- Authentication and Security
- Session Management
- WebSocket Implementation
- Microservices Architecture
- Testing and Benchmarking
- Deployment Strategies
- Performance Optimization
- Monitoring and Logging
- Troubleshooting
- Best Practices
- Conclusion
Prerequisites
Before starting, ensure you have:
- Alpine Linux installed and updated
- Root or sudo access
- Basic understanding of Go programming
- 2GB+ RAM for development
- Internet connection for package downloads
- Git installed for version control
Installing Go on Alpine
Step 1: Install Go from Packages
# Update package repository
apk update
# Install Go
apk add go
# Install additional development tools
apk add git make gcc musl-dev
# Verify installation
go version
Step 2: Install Latest Go Version
# Download latest Go (check https://golang.org/dl/ for latest version)
cd /tmp
wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
# Remove existing Go installation
rm -rf /usr/local/go
# Extract new version
tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz
# Set up environment variables
cat >> /etc/profile << 'EOF'
export PATH=$PATH:/usr/local/go/bin
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
EOF
# Apply changes
source /etc/profile
# Verify installation
go version
Step 3: Configure Go Workspace
# Create Go workspace
mkdir -p ~/go/{bin,src,pkg}
# Set up user environment
cat >> ~/.bashrc << 'EOF'
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
export GO111MODULE=on
export GOPROXY=https://proxy.golang.org,direct
EOF
source ~/.bashrc
Development Environment Setup
Step 1: Install Development Tools
# Install essential tools
go install golang.org/x/tools/gopls@latest
go install github.com/go-delve/delve/cmd/dlv@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install github.com/air-verse/air@latest
# Install database drivers
go get -u github.com/lib/pq
go get -u github.com/go-sql-driver/mysql
go get -u github.com/mattn/go-sqlite3
Step 2: Create Development Scripts
# Create project initialization script
cat > /usr/local/bin/go-init-project << 'EOF'
#!/bin/sh
# Initialize new Go project
PROJECT_NAME=$1
if [ -z "$PROJECT_NAME" ]; then
echo "Usage: go-init-project <project-name>"
exit 1
fi
# Create project structure
mkdir -p $PROJECT_NAME/{cmd,internal,pkg,api,web/static,web/templates,configs,scripts,test}
cd $PROJECT_NAME
# Initialize Go module
go mod init github.com/$(whoami)/$PROJECT_NAME
# Create main.go
cat > cmd/main.go << 'EOG'
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", homeHandler)
http.HandleFunc("/health", healthHandler)
log.Printf("Server starting on port %s", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatal(err)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to %s!", "PROJECT_NAME")
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}
EOG
# Create Makefile
cat > Makefile << 'EOM'
.PHONY: build run test clean
build:
go build -o bin/app cmd/main.go
run:
go run cmd/main.go
test:
go test -v ./...
clean:
rm -rf bin/
docker-build:
docker build -t $(PROJECT_NAME) .
docker-run:
docker run -p 8080:8080 $(PROJECT_NAME)
EOM
# Create .gitignore
cat > .gitignore << 'EOI'
# Binaries
*.exe
*.exe~
*.dll
*.so
*.dylib
bin/
# Test binary
*.test
# Output
*.out
# Go workspace
go.work
# Environment
.env
*.env
# IDE
.idea/
.vscode/
*.swp
*.swo
EOI
# Create README
cat > README.md << 'EOR'
# PROJECT_NAME
## Description
Go web application on Alpine Linux
## Quick Start
\`\`\`bash
make build
make run
\`\`\`
## Development
\`\`\`bash
air # Hot reload
make test # Run tests
\`\`\`
EOR
sed -i "s/PROJECT_NAME/$PROJECT_NAME/g" cmd/main.go
sed -i "s/PROJECT_NAME/$PROJECT_NAME/g" README.md
echo "Project $PROJECT_NAME initialized successfully!"
EOF
chmod +x /usr/local/bin/go-init-project
Step 3: Hot Reload Configuration
# Create air configuration
cat > .air.toml << 'EOF'
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/main.go"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
log = "build-errors.log"
send_interrupt = false
stop_on_error = true
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
EOF
Creating a Basic Web Application
Step 1: Web Server with Routing
// File: cmd/web/main.go
package main
import (
"context"
"encoding/json"
"html/template"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gorilla/mux"
"github.com/gorilla/handlers"
)
type Server struct {
router *mux.Router
logger *log.Logger
}
func NewServer() *Server {
return &Server{
router: mux.NewRouter(),
logger: log.New(os.Stdout, "[SERVER] ", log.LstdFlags),
}
}
func (s *Server) ConfigureRoutes() {
// Static files
s.router.PathPrefix("/static/").Handler(
http.StripPrefix("/static/", http.FileServer(http.Dir("./web/static/"))),
)
// API routes
api := s.router.PathPrefix("/api/v1").Subrouter()
api.Use(s.jsonMiddleware)
api.HandleFunc("/users", s.handleUsers).Methods("GET")
api.HandleFunc("/users/{id}", s.handleUser).Methods("GET")
api.HandleFunc("/users", s.createUser).Methods("POST")
// Web routes
s.router.HandleFunc("/", s.handleHome).Methods("GET")
s.router.HandleFunc("/about", s.handleAbout).Methods("GET")
// Health check
s.router.HandleFunc("/health", s.handleHealth).Methods("GET")
}
func (s *Server) jsonMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
next.ServeHTTP(w, r)
})
}
func (s *Server) handleHome(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("web/templates/layout.html", "web/templates/home.html"))
data := struct {
Title string
Message string
}{
Title: "Home",
Message: "Welcome to Go on Alpine Linux!",
}
tmpl.Execute(w, data)
}
func (s *Server) handleAbout(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("web/templates/layout.html", "web/templates/about.html"))
data := struct {
Title string
}{
Title: "About",
}
tmpl.Execute(w, data)
}
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
response := map[string]string{
"status": "healthy",
"time": time.Now().Format(time.RFC3339),
}
json.NewEncoder(w).Encode(response)
}
func (s *Server) handleUsers(w http.ResponseWriter, r *http.Request) {
users := []map[string]interface{}{
{"id": 1, "name": "John Doe", "email": "[email protected]"},
{"id": 2, "name": "Jane Smith", "email": "[email protected]"},
}
json.NewEncoder(w).Encode(users)
}
func (s *Server) handleUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
user := map[string]interface{}{
"id": id,
"name": "John Doe",
"email": "[email protected]",
}
json.NewEncoder(w).Encode(user)
}
func (s *Server) createUser(w http.ResponseWriter, r *http.Request) {
var user map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user["id"] = 3
user["created_at"] = time.Now()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func (s *Server) Run(addr string) error {
srv := &http.Server{
Addr: addr,
Handler: handlers.LoggingHandler(os.Stdout, s.router),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Graceful shutdown
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)
<-sigint
s.logger.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
s.logger.Printf("Server forced to shutdown: %v", err)
}
}()
s.logger.Printf("Server starting on %s", addr)
return srv.ListenAndServe()
}
func main() {
server := NewServer()
server.ConfigureRoutes()
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
if err := server.Run(":" + port); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}
Step 2: HTML Templates
<!-- File: web/templates/layout.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}} - Go Web App</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/api/v1/users">API</a></li>
</ul>
</nav>
<main>
{{block "content" .}}{{end}}
</main>
<footer>
<p>© 2024 Go Web Application on Alpine Linux</p>
</footer>
<script src="/static/js/app.js"></script>
</body>
</html>
<!-- File: web/templates/home.html -->
{{define "content"}}
<h1>{{.Message}}</h1>
<p>This is a Go web application running on Alpine Linux.</p>
<div id="users">
<h2>Users</h2>
<button onclick="loadUsers()">Load Users</button>
<div id="userList"></div>
</div>
{{end}}
Step 3: Static Assets
/* File: web/static/css/style.css */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
nav {
background-color: #333;
color: white;
padding: 1rem;
}
nav ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
}
nav ul li {
margin-right: 1rem;
}
nav ul li a {
color: white;
text-decoration: none;
}
main {
max-width: 1200px;
margin: 2rem auto;
padding: 0 1rem;
background-color: white;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
min-height: 400px;
padding: 2rem;
}
footer {
text-align: center;
padding: 1rem;
background-color: #333;
color: white;
position: fixed;
bottom: 0;
width: 100%;
}
// File: web/static/js/app.js
async function loadUsers() {
try {
const response = await fetch('/api/v1/users');
const users = await response.json();
const userList = document.getElementById('userList');
userList.innerHTML = '<ul>' +
users.map(user => `<li>${user.name} - ${user.email}</li>`).join('') +
'</ul>';
} catch (error) {
console.error('Error loading users:', error);
}
}
Database Integration
Step 1: PostgreSQL Integration
// File: internal/database/postgres.go
package database
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
type Config struct {
Host string
Port int
User string
Password string
DBName string
SSLMode string
}
type DB struct {
*sql.DB
}
func NewConnection(cfg Config) (*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 := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
// Test connection
if err := db.Ping(); err != nil {
return nil, err
}
return &DB{db}, nil
}
func (db *DB) Migrate() error {
query := `
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(64) PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
`
_, err := db.Exec(query)
return err
}
Step 2: Repository Pattern
// File: internal/repository/user.go
package repository
import (
"database/sql"
"errors"
"time"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
PasswordHash string `json:"-"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type UserRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) Create(user *User) error {
query := `
INSERT INTO users (username, email, password_hash)
VALUES ($1, $2, $3)
RETURNING id, created_at, updated_at
`
err := r.db.QueryRow(query, user.Username, user.Email, user.PasswordHash).
Scan(&user.ID, &user.CreatedAt, &user.UpdatedAt)
return err
}
func (r *UserRepository) GetByID(id int) (*User, error) {
user := &User{}
query := `
SELECT id, username, email, password_hash, created_at, updated_at
FROM users
WHERE id = $1
`
err := r.db.QueryRow(query, id).Scan(
&user.ID, &user.Username, &user.Email,
&user.PasswordHash, &user.CreatedAt, &user.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, errors.New("user not found")
}
return user, err
}
func (r *UserRepository) GetByUsername(username string) (*User, error) {
user := &User{}
query := `
SELECT id, username, email, password_hash, created_at, updated_at
FROM users
WHERE username = $1
`
err := r.db.QueryRow(query, username).Scan(
&user.ID, &user.Username, &user.Email,
&user.PasswordHash, &user.CreatedAt, &user.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, errors.New("user not found")
}
return user, err
}
func (r *UserRepository) Update(user *User) error {
query := `
UPDATE users
SET username = $1, email = $2, updated_at = CURRENT_TIMESTAMP
WHERE id = $3
`
_, err := r.db.Exec(query, user.Username, user.Email, user.ID)
return err
}
func (r *UserRepository) Delete(id int) error {
_, err := r.db.Exec("DELETE FROM users WHERE id = $1", id)
return err
}
func (r *UserRepository) List(offset, limit int) ([]*User, error) {
query := `
SELECT id, username, email, password_hash, created_at, updated_at
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
rows, err := r.db.Query(query, limit, offset)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
err := rows.Scan(
&user.ID, &user.Username, &user.Email,
&user.PasswordHash, &user.CreatedAt, &user.UpdatedAt,
)
if err != nil {
return nil, err
}
users = append(users, user)
}
return users, rows.Err()
}
RESTful API Development
Step 1: API Structure
// File: internal/api/handlers.go
package api
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/yourapp/internal/repository"
)
type API struct {
userRepo *repository.UserRepository
}
func NewAPI(userRepo *repository.UserRepository) *API {
return &API{
userRepo: userRepo,
}
}
type ErrorResponse struct {
Error string `json:"error"`
}
type SuccessResponse struct {
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func (a *API) respondWithError(w http.ResponseWriter, code int, message string) {
a.respondWithJSON(w, code, ErrorResponse{Error: message})
}
func (a *API) respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, err := json.Marshal(payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"Error marshaling JSON"}`))
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
// User handlers
func (a *API) GetUsers(w http.ResponseWriter, r *http.Request) {
offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
if limit == 0 || limit > 100 {
limit = 20
}
users, err := a.userRepo.List(offset, limit)
if err != nil {
a.respondWithError(w, http.StatusInternalServerError, "Error fetching users")
return
}
a.respondWithJSON(w, http.StatusOK, users)
}
func (a *API) GetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
a.respondWithError(w, http.StatusBadRequest, "Invalid user ID")
return
}
user, err := a.userRepo.GetByID(id)
if err != nil {
a.respondWithError(w, http.StatusNotFound, "User not found")
return
}
a.respondWithJSON(w, http.StatusOK, user)
}
type CreateUserRequest struct {
Username string `json:"username" validate:"required,min=3,max=50"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
func (a *API) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
a.respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
// Hash password (implement proper hashing)
passwordHash := hashPassword(req.Password)
user := &repository.User{
Username: req.Username,
Email: req.Email,
PasswordHash: passwordHash,
}
if err := a.userRepo.Create(user); err != nil {
a.respondWithError(w, http.StatusInternalServerError, "Error creating user")
return
}
a.respondWithJSON(w, http.StatusCreated, user)
}
func (a *API) UpdateUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
a.respondWithError(w, http.StatusBadRequest, "Invalid user ID")
return
}
var req struct {
Username string `json:"username"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
a.respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
user, err := a.userRepo.GetByID(id)
if err != nil {
a.respondWithError(w, http.StatusNotFound, "User not found")
return
}
user.Username = req.Username
user.Email = req.Email
if err := a.userRepo.Update(user); err != nil {
a.respondWithError(w, http.StatusInternalServerError, "Error updating user")
return
}
a.respondWithJSON(w, http.StatusOK, user)
}
func (a *API) DeleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
a.respondWithError(w, http.StatusBadRequest, "Invalid user ID")
return
}
if err := a.userRepo.Delete(id); err != nil {
a.respondWithError(w, http.StatusInternalServerError, "Error deleting user")
return
}
a.respondWithJSON(w, http.StatusOK, SuccessResponse{Message: "User deleted successfully"})
}
Step 2: API Middleware
// File: internal/middleware/middleware.go
package middleware
import (
"context"
"log"
"net/http"
"strings"
"time"
"github.com/google/uuid"
)
type contextKey string
const RequestIDKey contextKey = "requestID"
// RequestID middleware
func RequestID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
ctx := context.WithValue(r.Context(), RequestIDKey, requestID)
w.Header().Set("X-Request-ID", requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Logger middleware
func Logger(logger *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &responseWriter{
ResponseWriter: w,
status: http.StatusOK,
}
next.ServeHTTP(wrapped, r)
logger.Printf(
"%s %s %s %d %s %s",
r.RemoteAddr,
r.Method,
r.URL.Path,
wrapped.status,
time.Since(start),
r.Header.Get("User-Agent"),
)
})
}
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}
// CORS middleware
func CORS(allowedOrigins []string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
for _, allowed := range allowedOrigins {
if allowed == "*" || allowed == origin {
w.Header().Set("Access-Control-Allow-Origin", origin)
break
}
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID")
w.Header().Set("Access-Control-Max-Age", "86400")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
}
// RateLimit middleware
type RateLimiter struct {
requests map[string][]time.Time
limit int
window time.Duration
}
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
return &RateLimiter{
requests: make(map[string][]time.Time),
limit: limit,
window: window,
}
}
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
now := time.Now()
// Clean old requests
if requests, exists := rl.requests[ip]; exists {
var valid []time.Time
for _, t := range requests {
if now.Sub(t) < rl.window {
valid = append(valid, t)
}
}
rl.requests[ip] = valid
}
// Check limit
if len(rl.requests[ip]) >= rl.limit {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
// Add current request
rl.requests[ip] = append(rl.requests[ip], now)
next.ServeHTTP(w, r)
})
}
Authentication and Security
Step 1: JWT Authentication
// File: internal/auth/jwt.go
package auth
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v4"
)
type Claims struct {
UserID int `json:"user_id"`
Username string `json:"username"`
jwt.RegisteredClaims
}
type JWTManager struct {
secretKey string
tokenDuration time.Duration
}
func NewJWTManager(secretKey string, tokenDuration time.Duration) *JWTManager {
return &JWTManager{
secretKey: secretKey,
tokenDuration: tokenDuration,
}
}
func (m *JWTManager) Generate(userID int, username string) (string, error) {
claims := &Claims{
UserID: userID,
Username: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.tokenDuration)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(m.secretKey))
}
func (m *JWTManager) Verify(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, errors.New("unexpected signing method")
}
return []byte(m.secretKey), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("invalid token")
}
return claims, nil
}
Step 2: Authentication Handlers
// File: internal/auth/handlers.go
package auth
import (
"encoding/json"
"net/http"
"strings"
"golang.org/x/crypto/bcrypt"
)
type AuthService struct {
userRepo *repository.UserRepository
jwtManager *JWTManager
}
func NewAuthService(userRepo *repository.UserRepository, jwtManager *JWTManager) *AuthService {
return &AuthService{
userRepo: userRepo,
jwtManager: jwtManager,
}
}
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResponse struct {
Token string `json:"token"`
ExpiresIn int64 `json:"expires_in"`
}
func (s *AuthService) Login(w http.ResponseWriter, r *http.Request) {
var req LoginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
user, err := s.userRepo.GetByUsername(req.Username)
if err != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil {
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
return
}
token, err := s.jwtManager.Generate(user.ID, user.Username)
if err != nil {
http.Error(w, "Error generating token", http.StatusInternalServerError)
return
}
response := LoginResponse{
Token: token,
ExpiresIn: int64(s.jwtManager.tokenDuration.Seconds()),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s *AuthService) 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, "Missing authorization header", http.StatusUnauthorized)
return
}
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
return
}
claims, err := s.jwtManager.Verify(parts[1])
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Add user info to context
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
ctx = context.WithValue(ctx, "username", claims.Username)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func hashPassword(password string) string {
bytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes)
}
Session Management
Step 1: Session Store
// File: internal/session/store.go
package session
import (
"crypto/rand"
"database/sql"
"encoding/base64"
"errors"
"net/http"
"time"
)
type Session struct {
ID string `json:"id"`
UserID int `json:"user_id"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
}
type Store struct {
db *sql.DB
cookieName string
cookiePath string
cookieDomain string
cookieSecure bool
cookieHTTPOnly bool
sessionLength time.Duration
}
func NewStore(db *sql.DB, cookieName string, sessionLength time.Duration) *Store {
return &Store{
db: db,
cookieName: cookieName,
cookiePath: "/",
cookieHTTPOnly: true,
sessionLength: sessionLength,
}
}
func (s *Store) generateID() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
func (s *Store) Create(userID int) (*Session, error) {
id, err := s.generateID()
if err != nil {
return nil, err
}
session := &Session{
ID: id,
UserID: userID,
ExpiresAt: time.Now().Add(s.sessionLength),
CreatedAt: time.Now(),
}
query := `
INSERT INTO sessions (id, user_id, expires_at, created_at)
VALUES ($1, $2, $3, $4)
`
_, err = s.db.Exec(query, session.ID, session.UserID, session.ExpiresAt, session.CreatedAt)
if err != nil {
return nil, err
}
return session, nil
}
func (s *Store) Get(id string) (*Session, error) {
session := &Session{}
query := `
SELECT id, user_id, expires_at, created_at
FROM sessions
WHERE id = $1 AND expires_at > NOW()
`
err := s.db.QueryRow(query, id).Scan(
&session.ID, &session.UserID, &session.ExpiresAt, &session.CreatedAt,
)
if err == sql.ErrNoRows {
return nil, errors.New("session not found or expired")
}
return session, err
}
func (s *Store) Delete(id string) error {
_, err := s.db.Exec("DELETE FROM sessions WHERE id = $1", id)
return err
}
func (s *Store) Cleanup() error {
_, err := s.db.Exec("DELETE FROM sessions WHERE expires_at < NOW()")
return err
}
func (s *Store) SetCookie(w http.ResponseWriter, session *Session) {
cookie := &http.Cookie{
Name: s.cookieName,
Value: session.ID,
Path: s.cookiePath,
Domain: s.cookieDomain,
Expires: session.ExpiresAt,
Secure: s.cookieSecure,
HttpOnly: s.cookieHTTPOnly,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
}
func (s *Store) GetFromRequest(r *http.Request) (*Session, error) {
cookie, err := r.Cookie(s.cookieName)
if err != nil {
return nil, err
}
return s.Get(cookie.Value)
}
func (s *Store) DeleteCookie(w http.ResponseWriter) {
cookie := &http.Cookie{
Name: s.cookieName,
Value: "",
Path: s.cookiePath,
Domain: s.cookieDomain,
MaxAge: -1,
Secure: s.cookieSecure,
HttpOnly: s.cookieHTTPOnly,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
}
WebSocket Implementation
Step 1: WebSocket Server
// File: internal/websocket/hub.go
package websocket
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// Configure origin checking for production
return true
},
}
type Message struct {
Type string `json:"type"`
UserID int `json:"user_id"`
Content interface{} `json:"content"`
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan Message
userID int
username string
}
type Hub struct {
clients map[*Client]bool
broadcast chan Message
register chan *Client
unregister chan *Client
}
func NewHub() *Hub {
return &Hub{
broadcast: make(chan Message),
register: make(chan *Client),
unregister: make(chan *Client),
clients: make(map[*Client]bool),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
log.Printf("Client %s connected", client.username)
// Notify other clients
h.broadcast <- Message{
Type: "user_joined",
UserID: client.userID,
Content: map[string]string{"username": client.username},
}
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
log.Printf("Client %s disconnected", client.username)
// Notify other clients
h.broadcast <- Message{
Type: "user_left",
UserID: client.userID,
Content: map[string]string{"username": client.username},
}
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
for {
var message Message
err := c.conn.ReadJSON(&message)
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
message.UserID = c.userID
c.hub.broadcast <- message
}
}
func (c *Client) writePump() {
defer c.conn.Close()
for {
select {
case message, ok := <-c.send:
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
c.conn.WriteJSON(message)
}
}
}
func ServeWS(hub *Hub, w http.ResponseWriter, r *http.Request) {
// Get user from context (set by auth middleware)
userID := r.Context().Value("user_id").(int)
username := r.Context().Value("username").(string)
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := &Client{
hub: hub,
conn: conn,
send: make(chan Message, 256),
userID: userID,
username: username,
}
client.hub.register <- client
go client.writePump()
go client.readPump()
}
Microservices Architecture
Step 1: Service Structure
# Create microservice structure
cat > /usr/local/bin/create-microservice << 'EOF'
#!/bin/sh
# Create Go microservice
SERVICE_NAME=$1
if [ -z "$SERVICE_NAME" ]; then
echo "Usage: create-microservice <service-name>"
exit 1
fi
mkdir -p services/$SERVICE_NAME/{cmd,internal,api,configs,deployments}
# Service main.go
cat > services/$SERVICE_NAME/cmd/main.go << 'EOG'
package main
import (
"context"
"log"
"net"
"os"
"os/signal"
"syscall"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
)
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "50051"
}
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
// Register your service here
// pb.RegisterYourServiceServer(s, &server{})
// Health check
grpc_health_v1.RegisterHealthServer(s, health.NewServer())
go func() {
log.Printf("gRPC server starting on port %s", port)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}()
// Graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down gRPC server...")
s.GracefulStop()
}
EOG
# Dockerfile
cat > services/$SERVICE_NAME/Dockerfile << 'EOD'
# Build stage
FROM golang:1.21-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main cmd/main.go
# Final stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
EXPOSE 50051
CMD ["./main"]
EOD
echo "Microservice $SERVICE_NAME created successfully!"
EOF
chmod +x /usr/local/bin/create-microservice
Step 2: API Gateway
// File: services/api-gateway/main.go
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/mux"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type Gateway struct {
userConn *grpc.ClientConn
productConn *grpc.ClientConn
orderConn *grpc.ClientConn
}
func NewGateway() (*Gateway, error) {
// Connect to microservices
userConn, err := grpc.Dial("user-service:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
productConn, err := grpc.Dial("product-service:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
orderConn, err := grpc.Dial("order-service:50053", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
return nil, err
}
return &Gateway{
userConn: userConn,
productConn: productConn,
orderConn: orderConn,
}, nil
}
func (g *Gateway) Close() {
g.userConn.Close()
g.productConn.Close()
g.orderConn.Close()
}
func (g *Gateway) handleUsers(w http.ResponseWriter, r *http.Request) {
// Proxy to user service
// Implementation depends on your protobuf definitions
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"service": "users"})
}
func (g *Gateway) handleProducts(w http.ResponseWriter, r *http.Request) {
// Proxy to product service
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"service": "products"})
}
func (g *Gateway) handleOrders(w http.ResponseWriter, r *http.Request) {
// Proxy to order service
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"service": "orders"})
}
func main() {
gateway, err := NewGateway()
if err != nil {
log.Fatal(err)
}
defer gateway.Close()
router := mux.NewRouter()
// API routes
api := router.PathPrefix("/api/v1").Subrouter()
api.HandleFunc("/users", gateway.handleUsers)
api.HandleFunc("/products", gateway.handleProducts)
api.HandleFunc("/orders", gateway.handleOrders)
// Health check
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "healthy"})
})
srv := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Println("API Gateway starting on :8080")
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Testing and Benchmarking
Step 1: Unit Tests
// File: internal/repository/user_test.go
package repository
import (
"database/sql"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
func TestUserRepository_Create(t *testing.T) {
db, mock, err := sqlmock.New()
assert.NoError(t, err)
defer db.Close()
repo := NewUserRepository(db)
user := &User{
Username: "testuser",
Email: "[email protected]",
PasswordHash: "hash",
}
rows := sqlmock.NewRows([]string{"id", "created_at", "updated_at"}).
AddRow(1, time.Now(), time.Now())
mock.ExpectQuery("INSERT INTO users").
WithArgs(user.Username, user.Email, user.PasswordHash).
WillReturnRows(rows)
err = repo.Create(user)
assert.NoError(t, err)
assert.Equal(t, 1, user.ID)
err = mock.ExpectationsWereMet()
assert.NoError(t, err)
}
func TestUserRepository_GetByID(t *testing.T) {
db, mock, err := sqlmock.New()
assert.NoError(t, err)
defer db.Close()
repo := NewUserRepository(db)
expectedUser := &User{
ID: 1,
Username: "testuser",
Email: "[email protected]",
PasswordHash: "hash",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
rows := sqlmock.NewRows([]string{"id", "username", "email", "password_hash", "created_at", "updated_at"}).
AddRow(expectedUser.ID, expectedUser.Username, expectedUser.Email,
expectedUser.PasswordHash, expectedUser.CreatedAt, expectedUser.UpdatedAt)
mock.ExpectQuery("SELECT (.+) FROM users WHERE id").
WithArgs(1).
WillReturnRows(rows)
user, err := repo.GetByID(1)
assert.NoError(t, err)
assert.Equal(t, expectedUser.Username, user.Username)
err = mock.ExpectationsWereMet()
assert.NoError(t, err)
}
Step 2: Integration Tests
// File: test/integration/api_test.go
package integration
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAPI_CreateUser(t *testing.T) {
// Setup test server
server := setupTestServer()
// Create user request
payload := map[string]string{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
// Record response
w := httptest.NewRecorder()
server.router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &response)
assert.Equal(t, "testuser", response["username"])
assert.Equal(t, "[email protected]", response["email"])
assert.NotNil(t, response["id"])
}
func TestAPI_GetUser(t *testing.T) {
server := setupTestServer()
// Create user first
user := createTestUser(t, server)
// Get user
req := httptest.NewRequest("GET", "/api/v1/users/"+user.ID, nil)
w := httptest.NewRecorder()
server.router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &response)
assert.Equal(t, user.Username, response["username"])
}
Step 3: Benchmarks
// File: internal/repository/user_bench_test.go
package repository
import (
"testing"
)
func BenchmarkUserRepository_Create(b *testing.B) {
db := setupTestDB()
defer db.Close()
repo := NewUserRepository(db)
b.ResetTimer()
for i := 0; i < b.N; i++ {
user := &User{
Username: fmt.Sprintf("user%d", i),
Email: fmt.Sprintf("user%d@example.com", i),
PasswordHash: "hash",
}
repo.Create(user)
}
}
func BenchmarkUserRepository_GetByID(b *testing.B) {
db := setupTestDB()
defer db.Close()
repo := NewUserRepository(db)
// Create test user
user := &User{
Username: "benchuser",
Email: "[email protected]",
PasswordHash: "hash",
}
repo.Create(user)
b.ResetTimer()
for i := 0; i < b.N; i++ {
repo.GetByID(user.ID)
}
}
// Run benchmarks with:
// go test -bench=. -benchmem ./...
Deployment Strategies
Step 1: Docker Deployment
# File: Dockerfile
# Build stage
FROM golang:1.21-alpine AS builder
# Install dependencies
RUN apk add --no-cache git ca-certificates
# Set working directory
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main cmd/main.go
# Final stage
FROM alpine:latest
# Install ca-certificates for HTTPS
RUN apk --no-cache add ca-certificates
WORKDIR /root/
# Copy binary from builder
COPY --from=builder /app/main .
COPY --from=builder /app/web ./web
COPY --from=builder /app/configs ./configs
# Expose port
EXPOSE 8080
# Run application
CMD ["./main"]
Step 2: Docker Compose
# File: docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- PORT=8080
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=appuser
- DB_PASSWORD=apppassword
- DB_NAME=appdb
- REDIS_URL=redis://redis:6379
depends_on:
- postgres
- redis
networks:
- app-network
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_USER=appuser
- POSTGRES_PASSWORD=apppassword
- POSTGRES_DB=appdb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- app-network
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- app-network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./certs:/etc/nginx/certs
depends_on:
- app
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
Step 3: Kubernetes Deployment
# File: k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-app
labels:
app: go-app
spec:
replicas: 3
selector:
matchLabels:
app: go-app
template:
metadata:
labels:
app: go-app
spec:
containers:
- name: go-app
image: your-registry/go-app:latest
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-secret
key: host
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: go-app-service
spec:
selector:
app: go-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
Performance Optimization
Step 1: Profiling
// File: cmd/main.go - Add profiling endpoints
import (
_ "net/http/pprof"
)
func main() {
// Enable profiling in development
if os.Getenv("ENABLE_PROFILING") == "true" {
go func() {
log.Println("Starting profiling server on :6060")
log.Println(http.ListenAndServe(":6060", nil))
}()
}
// Your application code...
}
// Profile CPU usage:
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
// Profile memory:
// go tool pprof http://localhost:6060/debug/pprof/heap
Step 2: Caching
// File: internal/cache/redis.go
package cache
import (
"context"
"encoding/json"
"time"
"github.com/go-redis/redis/v8"
)
type Cache struct {
client *redis.Client
}
func NewCache(addr string) *Cache {
client := redis.NewClient(&redis.Options{
Addr: addr,
Password: "",
DB: 0,
})
return &Cache{client: client}
}
func (c *Cache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
json, err := json.Marshal(value)
if err != nil {
return err
}
return c.client.Set(ctx, key, json, expiration).Err()
}
func (c *Cache) Get(ctx context.Context, key string, dest interface{}) error {
val, err := c.client.Get(ctx, key).Result()
if err != nil {
return err
}
return json.Unmarshal([]byte(val), dest)
}
func (c *Cache) Delete(ctx context.Context, key string) error {
return c.client.Del(ctx, key).Err()
}
// Middleware for caching
func CacheMiddleware(cache *Cache, duration time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
next.ServeHTTP(w, r)
return
}
key := r.URL.Path
// Try to get from cache
var cachedResponse []byte
if err := cache.Get(r.Context(), key, &cachedResponse); err == nil {
w.Header().Set("X-Cache", "HIT")
w.Write(cachedResponse)
return
}
// Record response
rec := &responseRecorder{ResponseWriter: w, body: &bytes.Buffer{}}
next.ServeHTTP(rec, r)
// Cache successful responses
if rec.status == http.StatusOK {
cache.Set(r.Context(), key, rec.body.Bytes(), duration)
}
w.Header().Set("X-Cache", "MISS")
})
}
}
Monitoring and Logging
Step 1: Structured Logging
// File: internal/logger/logger.go
package logger
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func NewLogger(env string) (*zap.Logger, error) {
var config zap.Config
if env == "production" {
config = zap.NewProductionConfig()
config.EncoderConfig.TimeKey = "timestamp"
config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
} else {
config = zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
}
config.OutputPaths = []string{"stdout"}
config.ErrorOutputPaths = []string{"stderr"}
return config.Build()
}
// Middleware for request logging
func LoggingMiddleware(logger *zap.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wrapped := &responseWriter{
ResponseWriter: w,
status: http.StatusOK,
}
next.ServeHTTP(wrapped, r)
logger.Info("request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.Int("status", wrapped.status),
zap.Duration("duration", time.Since(start)),
zap.String("user_agent", r.UserAgent()),
zap.String("remote_addr", r.RemoteAddr),
)
})
}
}
Step 2: Metrics with Prometheus
// File: internal/metrics/metrics.go
package metrics
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type Metrics struct {
requestsTotal *prometheus.CounterVec
requestDuration *prometheus.HistogramVec
activeRequests prometheus.Gauge
}
func NewMetrics() *Metrics {
m := &Metrics{
requestsTotal: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status"},
),
requestDuration: prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint"},
),
activeRequests: prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "http_requests_active",
Help: "Number of active HTTP requests",
},
),
}
prometheus.MustRegister(m.requestsTotal)
prometheus.MustRegister(m.requestDuration)
prometheus.MustRegister(m.activeRequests)
return m
}
func (m *Metrics) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
m.activeRequests.Inc()
defer m.activeRequests.Dec()
wrapped := &responseWriter{
ResponseWriter: w,
status: http.StatusOK,
}
next.ServeHTTP(wrapped, r)
duration := time.Since(start).Seconds()
m.requestsTotal.WithLabelValues(
r.Method,
r.URL.Path,
http.StatusText(wrapped.status),
).Inc()
m.requestDuration.WithLabelValues(
r.Method,
r.URL.Path,
).Observe(duration)
})
}
func (m *Metrics) Handler() http.Handler {
return promhttp.Handler()
}
Troubleshooting
Common Issues
- Module dependencies issues:
# Clear module cache
go clean -modcache
# Download dependencies
go mod download
# Verify dependencies
go mod verify
# Update dependencies
go get -u ./...
- Build failures on Alpine:
# Install build dependencies
apk add gcc musl-dev
# For CGO dependencies
CGO_ENABLED=1 go build
# For static binary
CGO_ENABLED=0 go build -ldflags="-w -s"
- Database connection issues:
# Check PostgreSQL connection
psql -h localhost -U user -d dbname
# Test with Go
go run -tags=debug main.go
Debug Configuration
// File: internal/debug/debug.go
// +build debug
package debug
import (
"log"
"net/http"
"runtime"
)
func init() {
log.Println("DEBUG MODE ENABLED")
// Enable verbose logging
log.SetFlags(log.LstdFlags | log.Lshortfile)
// Memory stats endpoint
http.HandleFunc("/debug/memstats", func(w http.ResponseWriter, r *http.Request) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Fprintf(w, "Alloc = %v MB\n", m.Alloc/1024/1024)
fmt.Fprintf(w, "TotalAlloc = %v MB\n", m.TotalAlloc/1024/1024)
fmt.Fprintf(w, "Sys = %v MB\n", m.Sys/1024/1024)
fmt.Fprintf(w, "NumGC = %v\n", m.NumGC)
})
}
Best Practices
Project Structure
# Recommended project structure
cat > PROJECT_STRUCTURE.md << 'EOF'
# Go Project Structure
. โโโ cmd/ # Application entrypoints โ โโโ api/ # API server โ โโโ worker/ # Background workers โ โโโ migrate/ # Database migrations โโโ internal/ # Private application code โ โโโ api/ # API handlers โ โโโ auth/ # Authentication โ โโโ config/ # Configuration โ โโโ database/ # Database connections โ โโโ middleware/ # HTTP middleware โ โโโ models/ # Data models โ โโโ repository/ # Data access layer โ โโโ service/ # Business logic โ โโโ validator/ # Input validation โโโ pkg/ # Public packages โโโ web/ # Web assets โ โโโ static/ # CSS, JS, images โ โโโ templates/ # HTML templates โโโ configs/ # Configuration files โโโ deployments/ # Deployment configurations โโโ docs/ # Documentation โโโ scripts/ # Build and deploy scripts โโโ test/ # Additional test files โโโ .gitignore โโโ .golangci.yml # Linter configuration โโโ Dockerfile โโโ Makefile โโโ README.md โโโ go.mod โโโ go.sum
EOF
Code Quality
# File: .golangci.yml
linters:
enable:
- gofmt
- golint
- govet
- errcheck
- staticcheck
- gosimple
- structcheck
- varcheck
- ineffassign
- deadcode
- typecheck
- gosec
- gocyclo
- dupl
- misspell
- unparam
- nakedret
- prealloc
- scopelint
- gocritic
- gochecknoinits
- gochecknoglobals
linters-settings:
gocyclo:
min-complexity: 15
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 2
run:
skip-dirs:
- vendor
- test
skip-files:
- ".*_test.go"
issues:
exclude-rules:
- path: _test\.go
linters:
- gosec
- dupl
Makefile
# File: Makefile
.PHONY: build run test clean docker-build docker-run lint
# Variables
BINARY_NAME=app
DOCKER_IMAGE=go-app:latest
# Build
build:
go build -ldflags="-w -s" -o bin/$(BINARY_NAME) cmd/main.go
# Run
run:
go run cmd/main.go
# Test
test:
go test -v -race -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html
# Benchmark
bench:
go test -bench=. -benchmem ./...
# Lint
lint:
golangci-lint run
# Clean
clean:
go clean
rm -rf bin/
rm -f coverage.out coverage.html
# Docker
docker-build:
docker build -t $(DOCKER_IMAGE) .
docker-run:
docker run -p 8080:8080 $(DOCKER_IMAGE)
# Database migrations
migrate-up:
migrate -path ./migrations -database "postgresql://user:pass@localhost/db?sslmode=disable" up
migrate-down:
migrate -path ./migrations -database "postgresql://user:pass@localhost/db?sslmode=disable" down
# Development with hot reload
dev:
air
# Generate
generate:
go generate ./...
Conclusion
Youโve successfully set up a comprehensive Go web application development environment on Alpine Linux. This setup includes everything from basic web servers to microservices architectures, with proper testing, deployment strategies, and monitoring. Alpine Linuxโs minimal footprint combined with Goโs efficiency creates an excellent platform for building high-performance web applications.
Remember to keep your dependencies updated, follow Go best practices, and leverage Alpineโs security features. With this foundation, youโre ready to build scalable, maintainable Go applications that can handle production workloads efficiently.