Go: Integrated Project: E-commerce API (Part 3)

Last updated: 2026-08-26

We have the architecture, we have the security—now, let's get it live.

From the first line of code to production-ready: Swagger documentation, graceful shutdown, Docker deployment, pprof tuning, and Prometheus monitoring. Bob's e-commerce API is ready to welcome real users.

1. You will learn



2. Story: The Night Before Launch

(1) Pain Points: No documentation, no monitoring, and manual deployment

Bob's e-commerce API is fully functional, but the operations team has refused to accept it:

"The operations team said, 'Without API documentation, the front end can't integrate with it. Without health checks, we don't know if the service is running. Without monitoring, we wouldn't even know if it went down. And we still have to manually SCP the binaries for deployment—it's way too primitive.'"

Launch Checklist:

TEXT 📖 Display only
❌ API docs → Frontend has to ask "what fields does this endpoint return?" every time
❌ Graceful shutdown → Kill process causes in-flight orders to be lost
❌ Docker deploy → SCP binary to server, start manually
❌ Performance monitoring → Don't know which part of the API is slow

(2) Lesson Objective: Production-Ready

TEXT 📖 Display only
✅ Swagger docs → Frontend self-service browsing
✅ Graceful Shutdown → Signal handling + wait for connections to close
✅ Docker Compose → One command to start the full stack
✅ pprof + Prometheus → Performance visualization


3. Full Implementation

▶ Example: Graceful Shutdown

⚙️ Prerequisite: Run go get github.com/mattn/go-sqlite3 (requires CGO; alternatively use modernc.org/sqlite for a pure Go driver)

GO 📖 Display only
// cmd/server/main.go
package main

import (
    "context"
    "database/sql"
    "encoding/json"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "ecommerce/internal/handler"
    "ecommerce/internal/repository"
    "ecommerce/internal/service"
    _ "github.com/mattn/go-sqlite3"
)

func main() {
    db, err := sql.Open("sqlite3", "./ecommerce.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)

    // Dependency injection
    userRepo := repository.NewUserRepository(db)
    productRepo := repository.NewProductRepository(db)
    orderRepo := repository.NewOrderRepository(db)

    userSvc := service.NewUserService(userRepo)
    productSvc := service.NewProductService(productRepo)
    orderSvc := service.NewOrderService(orderRepo, productRepo, userRepo)

    userHandler := handler.NewUserHandler(userSvc)
    productHandler := handler.NewProductHandler(productSvc)
    orderHandler := handler.NewOrderHandler(orderSvc)

    mux := http.NewServeMux()
    userHandler.Register(mux)
    productHandler.Register(mux)
    orderHandler.Register(mux)

    // Health check endpoint
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
    })

    mux.HandleFunc("GET /ready", func(w http.ResponseWriter, r *http.Request) {
        if err := db.Ping(); err != nil {
            w.WriteHeader(http.StatusServiceUnavailable)
            json.NewEncoder(w).Encode(map[string]string{"status": "not ready"})
            return
        }
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]string{"status": "ready"})
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
        // Timeout settings (prevent slowloris attacks)
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       60 * time.Second,
        ReadHeaderTimeout: 5 * time.Second,
    }

    // Graceful Shutdown
    go func() {
        sigCh := make(chan os.Signal, 1)
        signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
        sig := <-sigCh
        log.Printf("Received signal %v, shutting down...", sig)

        // Give in-flight requests up to 30 seconds to complete
        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()

        if err := server.Shutdown(ctx); err != nil {
            log.Printf("Forced shutdown: %v", err)
        }
    }()

    log.Println("E-commerce API listening on :8080")
    if err := server.ListenAndServe(); err != http.ErrServerClosed {
        log.Fatal(err)
    }
    log.Println("Server shut down safely")
}
76 logic lines (exceeds 40-line limit, display only)

▶ Example: pprof + Prometheus Integration

⚙️ Prerequisite: Run go get github.com/prometheus/client_golang/prometheus and go get github.com/prometheus/client_golang/prometheus/promhttp

GO 📖 Display only
// monitoring.go
package main

import (
    "net/http"
    "net/http/pprof"
    "runtime"
    "strconv"
    "time"

    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// Prometheus metrics
var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total HTTP requests",
        },
        []string{"method", "path", "status"},
    )

    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5},
        },
        []string{"method", "path"},
    )

    activeGoroutines = prometheus.NewGaugeFunc(
        prometheus.GaugeOpts{
            Name: "go_goroutines_active",
            Help: "Current number of goroutines",
        },
        func() float64 {
            return float64(runtime.NumGoroutine())
        },
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(httpRequestDuration)
    prometheus.MustRegister(activeGoroutines)
}

// Prometheus middleware
func prometheusMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        // Wrap ResponseWriter to get status code
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start)
        httpRequestsTotal.WithLabelValues(
            r.Method, r.URL.Path, strconv.Itoa(wrapped.statusCode),
        ).Inc()
        httpRequestDuration.WithLabelValues(
            r.Method, r.URL.Path,
        ).Observe(duration.Seconds())
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// Register pprof and Prometheus endpoints in main
func registerMonitoring(mux *http.ServeMux) {
    // pprof endpoints
    mux.HandleFunc("/debug/pprof/", pprof.Index)
    mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
    mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
    mux.HandleFunc("/debug/pprof/trace", pprof.Trace)

    // Prometheus metrics
    mux.Handle("/metrics", promhttp.Handler())
}

func main() {
    mux := http.NewServeMux()
    registerMonitoring(mux)

    // Example business endpoint
    mux.HandleFunc("GET /api/products", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"products":[]}`))
    })

    // Health check
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"status":"ok"}`))
    })

    log.Println("Monitoring + API server listening on :8080")
    http.ListenAndServe(":8080", prometheusMiddleware(mux))
}
85 logic lines (exceeds 40-line limit, display only)

▶ Example: Docker Deployment

DOCKERFILE
# Dockerfile (Multi-stage Build)
FROM golang:1.22-alpine AS builder
WORKDIR /app

# Dependency cache
COPY go.mod go.sum ./
RUN go mod download

# Source + compile
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server ./cmd/server

# === Run stage ===
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata

COPY --from=builder /app/server /server

EXPOSE 8080
EXPOSE 6060  # pprof

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD wget -qO- http://localhost:8080/health || exit 1

CMD ["/server"]
YAML
# docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
      - "6060:6060"   # pprof
    environment:
      - DB_PATH=/data/ecommerce.db
      - GIN_MODE=release
    volumes:
      - data:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana

volumes:
  data:
  prometheus_data:
  grafana_data:
YAML
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'ecommerce-api'
    static_configs:
      - targets: ['api:8080']
    metrics_path: '/metrics'

▶ Example: Swagger Documentation

GO
// Generate OpenAPI docs from comments (use with swag tool)

// Package handler handles HTTP requests.
//
// E-commerce API
//
//     Schemes: http
//     Host: localhost:8080
//     BasePath: /api/v1
//     Version: 1.0.0
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
// swagger:meta
package handler

import "ecommerce/pkg/model"

// User information
// swagger:model
type User struct {
    ID    int    `json:"id"`
    Email سلسلة `json:"email"`
    Name  سلسلة `json:"name"`
}

// RegisterUser registers a new user
// swagger:route POST /api/v1/register users registerUser
//
// Register a new user
//
//     Consumes:
//     - application/json
//
//     Produces:
//     - application/json
//
//     Responses:
//       201: User
//       400: ErrorResponse
//       409: ErrorResponse
func (h *UserHandler) RegisterUser(w http.ResponseWriter, r *http.Request) {
    // handler logic
}

// swagger:parameters registerUser
type RegisterUserParams struct {
    // in: body
    Body model.RegisterRequest
}

// ErrorResponse خطأ استجابة
// swagger:استجابة
type ErrorResponse struct {
    // in: body
    Body struct {
        Error سلسلة `json:"خطأ"`
    }
}
▶ Try it Yourself
BASH
# Install swag CLI
$ go install github.com/swaggo/swag/cmd/swag@latest

# Generate docs
$ swag init -g cmd/server/main.go

# Access docs
# http://localhost:8080/swagger/index.html

(5) Launch Checklist

TEXT 📖 Display only
✅ Graceful Shutdown — wait for requests to complete after kill
✅ Health Check — /health and /ready endpoints
✅ pprof — /debug/pprof/ performance analysis
✅ Prometheus — /metrics endpoint exposed
✅ Swagger — Auto-generated API documentation
✅ Docker — Multi-stage build, image < 20 MB
✅ docker-compose — One command to start the full stack
✅ ReadTimeout / WriteTimeout — prevent slowloris attacks
✅ DB connection pool — MaxOpenConns=25, MaxIdleConns=10
✅ server.Shutdown — SIGINT/SIGTERM handling
💡 Tip: Production environments also require: (1) a reverse proxy (Nginx / Caddy) to handle TLS and domain names; (2) a CI/CD pipeline for automated builds and deployments; (3) centralized log management (such as Loki or Elasticsearch); (4) Alerting rules (e.g., trigger an alert when the 5xx error rate exceeds 1%). These topics are beyond the scope of this lesson, but it is recommended that you have them set up before deployment.


❓ FAQ

Q Why is a graceful shutdown important?
A When a SIGTERM signal is received (such as when Kubernetes terminates a Pod), server.Shutdown waits for all active HTTP requests to complete (up to the specified timeout) before closing the listener. If the process is killed immediately, requests currently being processed will be interrupted—resulting in inconsistent order data.
Q What are the server timeout settings?
A (1) ReadTimeout: Timeout for reading the entire request (including the body); (2) WriteTimeout: Timeout for sending a response; (3) IdleTimeout: Idle timeout for Keep-Alive connections; (4) ReadHeaderTimeout: The timeout for reading request headers. It is recommended to configure all of these settings to prevent slow-connection attacks.
Q Is pprof safe in a production environment?
A The pprof endpoint should not be exposed to the outside world. Solution: (1) Serve it on an internal port (e.g., :6060) that is not shared with business ports; (2) Protect the /debug/pprof/ path with auth middleware; (3) Enable it only in the staging environment. In the production environment, it is recommended to enable profiling temporarily only when needed.
Q How do I expose Prometheus metrics?
A Use promhttp.Handler() to expose metrics at the /metrics endpoint. The Prometheus server periodically scrapes this endpoint. Grafana connects to the Prometheus data source and creates visualizations. Standard metrics: number of requests, latency distribution, error rate, number of goroutines, and number of GC events.
Q How do I automatically generate OpenAPI documentation?
A Use the swaggo/swag tool—write comments in a specific format within your handler code, then run swag init to generate the docs/ directory. Use swaggo/http-swagger to mount the documentation to the /swagger/ endpoint. Comment format: // swagger:route, // swagger:model, // swagger:parameters.
Q How do you orchestrate multiple services with Docker Compose?
A Define multiple services (API, Prometheus, Grafana), use depends_on to control the startup order, and use volumes to persist data. Run docker-compose up -d to start all services with a single command. In a production environment, use Docker Stack or Kubernetes.
Q Are there three levels of granularity for health checks?
A (1) /health (alive) — The simplest check; a 200 response indicates the process is running; (2) /ready (ready) — Checks whether dependencies (database, cache) are reachable; (3) /status (detailed) — returns the status and latency of all dependencies.

📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Add graceful shutdown and timeout settings to the e-commerce API covered in this lesson. Use curl to test the /health and /ready endpoints. Use kill -SIGTERM <pid> to test the graceful shutdown.

  2. Advanced (Difficulty ⭐⭐): Implement a complete Docker-based deployment. Requirements: (1) Dockerfile with multi-stage build; (2) docker-compose.yml (API + Prometheus + Grafana); (3) HEALTHCHECK; (4) pprof endpoint (internal port only); (5) Verify that all services start normally after running docker-compose up.

  3. Challenge (Difficulty ⭐⭐⭐): Implement Prometheus monitoring + Grafana dashboards. Requirements: (1) Add Prometheus middleware to the API (request count, latency, error rate); (2) Add business metrics (e.g., number of orders created, number of registered users); (3) Include Prometheus and Grafana in the Docker Compose file; (4) Import or create a Grafana dashboard to display QPS, P99 latency, error rate, and number of goroutines; (5) Verify that the metrics are correct using Grafana after load testing.

🎉 Congratulations! You've completed all 30 lessons of the Go tutorial! From "Hello World" to a production-ready e-commerce API, you've mastered the full skill set for Go backend development.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏