This website requires JavaScript.
Golang Web API Template
main.go
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
    "golang-app/internal/config"
    "golang-app/internal/handlers"
    "golang-app/internal/middleware"
    "golang-app/internal/repository"
    "golang-app/internal/service"
)

func main() {
    // Load configuration
    cfg, err := config.Load()
    if err != nil {
        log.Fatal("Failed to load config:", err)
    }

    // Initialize repository layer
    repo := repository.NewMemoryRepository()

    // Initialize service layer
    svc := service.New(repo)

    // Initialize handlers
    h := handlers.New(svc)

    // Setup Gin router
    if cfg.Environment == "production" {
        gin.SetMode(gin.ReleaseMode)
    }

    router := gin.New()
    router.Use(gin.Logger())
    router.Use(gin.Recovery())
    router.Use(middleware.CORS())
    router.Use(middleware.RequestID())

    // Health check endpoint
    router.GET("/health", h.HealthCheck)

    // API routes
    v1 := router.Group("/api/v1")
    {
        v1.GET("/users", h.GetUsers)
        v1.POST("/users", h.CreateUser)
        v1.GET("/users/:id", h.GetUser)
        v1.PUT("/users/:id", h.UpdateUser)
        v1.DELETE("/users/:id", h.DeleteUser)
    }

    // Create HTTP server
    server := &http.Server{
        Addr:         fmt.Sprintf(":%s", cfg.Port),
        Handler:      router,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Start server in a goroutine
    go func() {
        log.Printf("Server starting on port %s", cfg.Port)
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatal("Failed to start server:", err)
        }
    }()

    // Wait for interrupt signal to gracefully shutdown the server
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    log.Println("Shutting down server...")

    // Graceful shutdown with timeout
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }

    log.Println("Server exited")
}
internal/config/config.go
package config

import (
    "os"
    "strconv"
)

// Config holds the application configuration
type Config struct {
    Port        string
    Environment string
    LogLevel    string
    Database    DatabaseConfig
}

// DatabaseConfig holds database configuration
type DatabaseConfig struct {
    Host     string
    Port     int
    Username string
    Password string
    Database string
    SSLMode  string
}

// Load loads configuration from environment variables with defaults
func Load() (*Config, error) {
    cfg := &Config{
        Port:        getEnv("PORT", "8080"),
        Environment: getEnv("ENVIRONMENT", "development"),
        LogLevel:    getEnv("LOG_LEVEL", "info"),
        Database: DatabaseConfig{
            Host:     getEnv("DB_HOST", "localhost"),
            Port:     getEnvAsInt("DB_PORT", 5432),
            Username: getEnv("DB_USERNAME", "postgres"),
            Password: getEnv("DB_PASSWORD", ""),
            Database: getEnv("DB_DATABASE", "golang_app"),
            SSLMode:  getEnv("DB_SSL_MODE", "disable"),
        },
    }

    return cfg, nil
}

// getEnv gets an environment variable or returns a default value
func getEnv(key, defaultValue string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return defaultValue
}

// getEnvAsInt gets an environment variable as integer or returns a default value
func getEnvAsInt(key string, defaultValue int) int {
    if valueStr := os.Getenv(key); valueStr != "" {
        if value, err := strconv.Atoi(valueStr); err == nil {
            return value
        }
    }
    return defaultValue
}
internal/models/user.go
package models

import (
    "time"
    "github.com/google/uuid"
)

// User represents a user in the system
type User struct {
    ID        string    `json:"id" validate:"required,uuid"`
    FirstName string    `json:"first_name" validate:"required,min=2,max=50"`
    LastName  string    `json:"last_name" validate:"required,min=2,max=50"`
    Email     string    `json:"email" validate:"required,email"`
    Age       int       `json:"age" validate:"required,min=1,max=120"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// CreateUserRequest represents the request payload for creating a user
type CreateUserRequest struct {
    FirstName string `json:"first_name" validate:"required,min=2,max=50"`
    LastName  string `json:"last_name" validate:"required,min=2,max=50"`
    Email     string `json:"email" validate:"required,email"`
    Age       int    `json:"age" validate:"required,min=1,max=120"`
}

// UpdateUserRequest represents the request payload for updating a user
type UpdateUserRequest struct {
    FirstName *string `json:"first_name,omitempty" validate:"omitempty,min=2,max=50"`
    LastName  *string `json:"last_name,omitempty" validate:"omitempty,min=2,max=50"`
    Email     *string `json:"email,omitempty" validate:"omitempty,email"`
    Age       *int    `json:"age,omitempty" validate:"omitempty,min=1,max=120"`
}

// NewUser creates a new user with generated ID and timestamps
func NewUser(req CreateUserRequest) *User {
    now := time.Now()
    return &User{
        ID:        uuid.New().String(),
        FirstName: req.FirstName,
        LastName:  req.LastName,
        Email:     req.Email,
        Age:       req.Age,
        CreatedAt: now,
        UpdatedAt: now,
    }
}

// Update updates user fields from UpdateUserRequest
func (u *User) Update(req UpdateUserRequest) {
    if req.FirstName != nil {
        u.FirstName = *req.FirstName
    }
    if req.LastName != nil {
        u.LastName = *req.LastName
    }
    if req.Email != nil {
        u.Email = *req.Email
    }
    if req.Age != nil {
        u.Age = *req.Age
    }
    u.UpdatedAt = time.Now()
}
internal/repository/repository.go
package repository

import (
    "errors"
    "sync"
    "golang-app/internal/models"
)

var (
    ErrUserNotFound     = errors.New("user not found")
    ErrUserAlreadyExists = errors.New("user already exists")
)

// UserRepository defines the interface for user data operations
type UserRepository interface {
    Create(user *models.User) error
    GetByID(id string) (*models.User, error)
    GetAll() ([]*models.User, error)
    Update(user *models.User) error
    Delete(id string) error
    ExistsByEmail(email string) bool
}

// MemoryRepository implements UserRepository using in-memory storage
type MemoryRepository struct {
    users map[string]*models.User
    mutex sync.RWMutex
}

// NewMemoryRepository creates a new in-memory repository
func NewMemoryRepository() UserRepository {
    return &MemoryRepository{
        users: make(map[string]*models.User),
        mutex: sync.RWMutex{},
    }
}

// Create adds a new user to the repository
func (r *MemoryRepository) Create(user *models.User) error {
    r.mutex.Lock()
    defer r.mutex.Unlock()

    if r.existsByEmail(user.Email) {
        return ErrUserAlreadyExists
    }

    r.users[user.ID] = user
    return nil
}

// GetByID retrieves a user by ID
func (r *MemoryRepository) GetByID(id string) (*models.User, error) {
    r.mutex.RLock()
    defer r.mutex.RUnlock()

    user, exists := r.users[id]
    if !exists {
        return nil, ErrUserNotFound
    }

    // Return a copy to prevent external modifications
    userCopy := *user
    return &userCopy, nil
}

// GetAll retrieves all users
func (r *MemoryRepository) GetAll() ([]*models.User, error) {
    r.mutex.RLock()
    defer r.mutex.RUnlock()

    users := make([]*models.User, 0, len(r.users))
    for _, user := range r.users {
        userCopy := *user
        users = append(users, &userCopy)
    }

    return users, nil
}

// Update updates an existing user
func (r *MemoryRepository) Update(user *models.User) error {
    r.mutex.Lock()
    defer r.mutex.Unlock()

    if _, exists := r.users[user.ID]; !exists {
        return ErrUserNotFound
    }

    r.users[user.ID] = user
    return nil
}

// Delete removes a user from the repository
func (r *MemoryRepository) Delete(id string) error {
    r.mutex.Lock()
    defer r.mutex.Unlock()

    if _, exists := r.users[id]; !exists {
        return ErrUserNotFound
    }

    delete(r.users, id)
    return nil
}

// ExistsByEmail checks if a user with the given email exists
func (r *MemoryRepository) ExistsByEmail(email string) bool {
    r.mutex.RLock()
    defer r.mutex.RUnlock()
    return r.existsByEmail(email)
}

// existsByEmail is the internal method without locking
func (r *MemoryRepository) existsByEmail(email string) bool {
    for _, user := range r.users {
        if user.Email == email {
            return true
        }
    }
    return false
}
internal/service/user_service.go
package service

import (
    "errors"
    "golang-app/internal/models"
    "golang-app/internal/repository"
)

var (
    ErrInvalidUserData = errors.New("invalid user data")
    ErrDuplicateEmail  = errors.New("email already exists")
)

// UserService defines the interface for user business logic
type UserService interface {
    CreateUser(req models.CreateUserRequest) (*models.User, error)
    GetUser(id string) (*models.User, error)
    GetAllUsers() ([]*models.User, error)
    UpdateUser(id string, req models.UpdateUserRequest) (*models.User, error)
    DeleteUser(id string) error
}

// Service implements UserService
type Service struct {
    repo repository.UserRepository
}

// New creates a new service instance
func New(repo repository.UserRepository) UserService {
    return &Service{
        repo: repo,
    }
}

// CreateUser creates a new user
func (s *Service) CreateUser(req models.CreateUserRequest) (*models.User, error) {
    // Check if user with email already exists
    if s.repo.ExistsByEmail(req.Email) {
        return nil, ErrDuplicateEmail
    }

    // Create new user
    user := models.NewUser(req)

    // Save to repository
    if err := s.repo.Create(user); err != nil {
        return nil, err
    }

    return user, nil
}

// GetUser retrieves a user by ID
func (s *Service) GetUser(id string) (*models.User, error) {
    if id == "" {
        return nil, ErrInvalidUserData
    }

    return s.repo.GetByID(id)
}

// GetAllUsers retrieves all users
func (s *Service) GetAllUsers() ([]*models.User, error) {
    return s.repo.GetAll()
}

// UpdateUser updates an existing user
func (s *Service) UpdateUser(id string, req models.UpdateUserRequest) (*models.User, error) {
    if id == "" {
        return nil, ErrInvalidUserData
    }

    // Get existing user
    user, err := s.repo.GetByID(id)
    if err != nil {
        return nil, err
    }

    // Check email uniqueness if email is being updated
    if req.Email != nil && *req.Email != user.Email {
        if s.repo.ExistsByEmail(*req.Email) {
            return nil, ErrDuplicateEmail
        }
    }

    // Update user
    user.Update(req)

    // Save changes
    if err := s.repo.Update(user); err != nil {
        return nil, err
    }

    return user, nil
}

// DeleteUser deletes a user
func (s *Service) DeleteUser(id string) error {
    if id == "" {
        return ErrInvalidUserData
    }

    return s.repo.Delete(id)
}
internal/handlers/handlers.go
package handlers

import (
    "net/http"
    "golang-app/internal/service"
    "golang-app/internal/models"
    "golang-app/internal/repository"

    "github.com/gin-gonic/gin"
    "github.com/go-playground/validator/v10"
)

// Handler holds the service dependencies
type Handler struct {
    service   service.UserService
    validator *validator.Validate
}

// New creates a new handler instance
func New(service service.UserService) *Handler {
    return &Handler{
        service:   service,
        validator: validator.New(),
    }
}

// ErrorResponse represents an error response
type ErrorResponse struct {
    Error   string `json:"error"`
    Message string `json:"message,omitempty"`
}

// SuccessResponse represents a success response
type SuccessResponse struct {
    Data    interface{} `json:"data"`
    Message string      `json:"message,omitempty"`
}

// HealthCheck handles health check requests
func (h *Handler) HealthCheck(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "status":  "healthy",
        "service": "golang-app",
        "version": "1.0.0",
    })
}

// CreateUser handles user creation
func (h *Handler) CreateUser(c *gin.Context) {
    var req models.CreateUserRequest

    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "invalid_request",
            Message: err.Error(),
        })
        return
    }

    if err := h.validator.Struct(req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "validation_error",
            Message: err.Error(),
        })
        return
    }

    user, err := h.service.CreateUser(req)
    if err != nil {
        switch err {
        case service.ErrDuplicateEmail:
            c.JSON(http.StatusConflict, ErrorResponse{
                Error:   "duplicate_email",
                Message: "User with this email already exists",
            })
        default:
            c.JSON(http.StatusInternalServerError, ErrorResponse{
                Error:   "internal_error",
                Message: "Failed to create user",
            })
        }
        return
    }

    c.JSON(http.StatusCreated, SuccessResponse{
        Data:    user,
        Message: "User created successfully",
    })
}

// GetUser handles getting a single user
func (h *Handler) GetUser(c *gin.Context) {
    id := c.Param("id")

    user, err := h.service.GetUser(id)
    if err != nil {
        switch err {
        case repository.ErrUserNotFound:
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "user_not_found",
                Message: "User not found",
            })
        default:
            c.JSON(http.StatusInternalServerError, ErrorResponse{
                Error:   "internal_error",
                Message: "Failed to get user",
            })
        }
        return
    }

    c.JSON(http.StatusOK, SuccessResponse{
        Data: user,
    })
}

// GetUsers handles getting all users
func (h *Handler) GetUsers(c *gin.Context) {
    users, err := h.service.GetAllUsers()
    if err != nil {
        c.JSON(http.StatusInternalServerError, ErrorResponse{
            Error:   "internal_error",
            Message: "Failed to get users",
        })
        return
    }

    c.JSON(http.StatusOK, SuccessResponse{
        Data: users,
    })
}

// UpdateUser handles user updates
func (h *Handler) UpdateUser(c *gin.Context) {
    id := c.Param("id")
    var req models.UpdateUserRequest

    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "invalid_request",
            Message: err.Error(),
        })
        return
    }

    if err := h.validator.Struct(req); err != nil {
        c.JSON(http.StatusBadRequest, ErrorResponse{
            Error:   "validation_error",
            Message: err.Error(),
        })
        return
    }

    user, err := h.service.UpdateUser(id, req)
    if err != nil {
        switch err {
        case repository.ErrUserNotFound:
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "user_not_found",
                Message: "User not found",
            })
        case service.ErrDuplicateEmail:
            c.JSON(http.StatusConflict, ErrorResponse{
                Error:   "duplicate_email",
                Message: "User with this email already exists",
            })
        default:
            c.JSON(http.StatusInternalServerError, ErrorResponse{
                Error:   "internal_error",
                Message: "Failed to update user",
            })
        }
        return
    }

    c.JSON(http.StatusOK, SuccessResponse{
        Data:    user,
        Message: "User updated successfully",
    })
}

// DeleteUser handles user deletion
func (h *Handler) DeleteUser(c *gin.Context) {
    id := c.Param("id")

    err := h.service.DeleteUser(id)
    if err != nil {
        switch err {
        case repository.ErrUserNotFound:
            c.JSON(http.StatusNotFound, ErrorResponse{
                Error:   "user_not_found",
                Message: "User not found",
            })
        default:
            c.JSON(http.StatusInternalServerError, ErrorResponse{
                Error:   "internal_error",
                Message: "Failed to delete user",
            })
        }
        return
    }

    c.JSON(http.StatusOK, SuccessResponse{
        Message: "User deleted successfully",
    })
}
internal/middleware/middleware.go
package middleware

import (
    "fmt"
    "time"

    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
    "github.com/google/uuid"
)

// CORS middleware for handling Cross-Origin Resource Sharing
func CORS() gin.HandlerFunc {
    return cors.New(cors.Config{
        AllowOrigins:     []string{"*"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"*"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour,
    })
}

// RequestID middleware adds a unique request ID to each request
func RequestID() gin.HandlerFunc {
    return func(c *gin.Context) {
        requestID := c.GetHeader("X-Request-ID")
        if requestID == "" {
            requestID = uuid.New().String()
        }

        c.Header("X-Request-ID", requestID)
        c.Set("RequestID", requestID)
        c.Next()
    }
}

// Logger middleware for structured logging
func Logger() gin.HandlerFunc {
    return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
            param.ClientIP,
            param.TimeStamp.Format(time.RFC1123),
            param.Method,
            param.Path,
            param.Request.Proto,
            param.StatusCode,
            param.Latency,
            param.Request.UserAgent(),
            param.ErrorMessage,
        )
    })
}
go.mod
module golang-app

go 1.23

require (
    github.com/gin-contrib/cors v1.7.0
    github.com/gin-gonic/gin v1.10.0
    github.com/go-playground/validator/v10 v10.23.0
    github.com/google/uuid v1.6.0
)

require (
    github.com/bytedance/sonic v1.12.5 // indirect
    github.com/bytedance/sonic/loader v0.2.1 // indirect
    github.com/cloudwego/base64x v0.1.4 // indirect
    github.com/cloudwego/iasm v0.2.0 // indirect
    github.com/gabriel-vasile/mimetype v1.4.7 // indirect
    github.com/gin-contrib/sse v0.1.0 // indirect
    github.com/go-playground/locales v0.14.1 // indirect
    github.com/go-playground/universal-translator v0.18.1 // indirect
    github.com/goccy/go-json v0.10.4 // indirect
    github.com/json-iterator/go v1.1.12 // indirect
    github.com/klauspost/cpuid/v2 v2.2.9 // indirect
    github.com/leodido/go-urn v1.4.0 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
    github.com/modern-go/reflect2 v1.0.2 // indirect
    github.com/pelletier/go-toml/v2 v2.2.3 // indirect
    github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
    github.com/ugorji/go/codec v1.2.12 // indirect
    golang.org/x/arch v0.12.0 // indirect
    golang.org/x/crypto v0.31.0 // indirect
    golang.org/x/net v0.33.0 // indirect
    golang.org/x/sys v0.28.0 // indirect
    golang.org/x/text v0.21.0 // indirect
    google.golang.org/protobuf v1.36.0 // indirect
    gopkg.in/yaml.v3 v3.0.1 // indirect
)
Dockerfile
# Build stage
FROM golang:1.23-alpine AS builder

# Install build dependencies
RUN apk add --no-cache git ca-certificates tzdata

# Set working directory
WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Final stage
FROM alpine:latest

# Install ca-certificates for HTTPS requests
RUN apk --no-cache add ca-certificates

# Set working directory
WORKDIR /root/

# Copy the binary from builder stage
COPY --from=builder /app/main .

# Expose port
EXPOSE 8080

# Set environment variables
ENV GIN_MODE=release
ENV PORT=8080

# Run the application
CMD ["./main"]
docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - ENVIRONMENT=development
      - LOG_LEVEL=info
      - DB_HOST=postgres
      - DB_PORT=5432
      - DB_USERNAME=golang_user
      - DB_PASSWORD=golang_pass
      - DB_DATABASE=golang_app
      - DB_SSL_MODE=disable
    depends_on:
      - postgres
    networks:
      - app-network

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=golang_user
      - POSTGRES_PASSWORD=golang_pass
      - POSTGRES_DB=golang_app
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:
Makefile
# Variables
APP_NAME=golang-app
BINARY_NAME=main
DOCKER_IMAGE=$(APP_NAME)
VERSION=1.0.0

# Go parameters
GOCMD=go
GOBUILD=$(GOCMD) build
GOCLEAN=$(GOCMD) clean
GOTEST=$(GOCMD) test
GOGET=$(GOCMD) get
GOMOD=$(GOCMD) mod
GOFMT=gofmt

.PHONY: all build clean test deps fmt vet run docker-build docker-run help

# Default target
all: clean deps fmt vet test build

# Build the application
build:
    @echo "Building $(APP_NAME)..."
    $(GOBUILD) -o $(BINARY_NAME) -v ./...

# Clean build files
clean:
    @echo "Cleaning..."
    $(GOCLEAN)
    rm -f $(BINARY_NAME)

# Run tests
test:
    @echo "Running tests..."
    $(GOTEST) -v ./...

# Run tests with coverage
test-coverage:
    @echo "Running tests with coverage..."
    $(GOTEST) -v -coverprofile=coverage.out ./...
    $(GOCMD) tool cover -html=coverage.out -o coverage.html

# Download dependencies
deps:
    @echo "Downloading dependencies..."
    $(GOMOD) download
    $(GOMOD) tidy

# Format code
fmt:
    @echo "Formatting code..."
    $(GOFMT) -s -w .

# Run go vet
vet:
    @echo "Running go vet..."
    $(GOCMD) vet ./...

# Run the application
run:
    @echo "Running $(APP_NAME)..."
    $(GOBUILD) -o $(BINARY_NAME) -v ./...
    ./$(BINARY_NAME)

# Run with hot reload (requires air: go install github.com/cosmtrek/air@latest)
dev:
    @echo "Running with hot reload..."
    air

# Build Docker image
docker-build:
    @echo "Building Docker image..."
    docker build -t $(DOCKER_IMAGE):$(VERSION) .
    docker tag $(DOCKER_IMAGE):$(VERSION) $(DOCKER_IMAGE):latest

# Run Docker container
docker-run:
    @echo "Running Docker container..."
    docker run -p 8080:8080 --env-file .env $(DOCKER_IMAGE):latest

# Run with docker-compose
docker-compose-up:
    @echo "Starting services with docker-compose..."
    docker-compose up --build

# Stop docker-compose services
docker-compose-down:
    @echo "Stopping docker-compose services..."
    docker-compose down

# Lint code (requires golangci-lint)
lint:
    @echo "Running linter..."
    golangci-lint run ./...

# Security scan (requires gosec)
security:
    @echo "Running security scan..."
    gosec ./...

# Install development tools
install-tools:
    @echo "Installing development tools..."
    go install github.com/cosmtrek/air@latest
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
    go install github.com/securecodewarrior/gosec/v2/cmd/gosec@latest

# Generate API documentation (requires swag)
docs:
    @echo "Generating API documentation..."
    swag init

# Help
help:
    @echo "Available commands:"
    @echo "  build              - Build the application"
    @echo "  clean              - Clean build files"
    @echo "  test               - Run tests"
    @echo "  test-coverage      - Run tests with coverage"
    @echo "  deps               - Download dependencies"
    @echo "  fmt                - Format code"
    @echo "  vet                - Run go vet"
    @echo "  run                - Run the application"
    @echo "  dev                - Run with hot reload"
    @echo "  docker-build       - Build Docker image"
    @echo "  docker-run         - Run Docker container"
    @echo "  docker-compose-up  - Start with docker-compose"
    @echo "  docker-compose-down- Stop docker-compose"
    @echo "  lint               - Run linter"
    @echo "  security           - Run security scan"
    @echo "  install-tools      - Install development tools"
    @echo "  docs               - Generate API documentation"
    @echo "  help               - Show this help"
.env.example
# Application Configuration
PORT=8080
ENVIRONMENT=development
LOG_LEVEL=info

# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=golang_user
DB_PASSWORD=golang_pass
DB_DATABASE=golang_app
DB_SSL_MODE=disable

# Redis Configuration (if needed)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=

# JWT Configuration (if auth is implemented)
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h

# External APIs (if needed)
API_BASE_URL=https://api.example.com
API_KEY=your-api-key
README.md

A production-ready REST API template built with Go, featuring clean architecture, comprehensive middleware, and Docker support.

## Features

- **Clean Architecture**: Separation of concerns with handlers, services, and repository layers
- **RESTful API**: Complete CRUD operations for user management
- **Middleware**: CORS, Request ID, logging, and recovery middleware
- **Configuration**: Environment-based configuration management
- **Validation**: Request validation using struct tags
- **Error Handling**: Structured error responses
- **Docker Support**: Multi-stage Dockerfile and docker-compose setup
- **Database Ready**: PostgreSQL and Redis integration configured
- **Production Ready**: Graceful shutdown, timeouts, and proper logging

## Quick Start

### Prerequisites

- Go 1.23 or higher
- Docker and Docker Compose (optional)
- PostgreSQL (if not using Docker)

### Local Development

1. Clone the repository:
```bash
git clone <repository-url>
cd golang-app
```

2. Install dependencies:
```bash
make deps
```

3. Copy environment file:
```bash
cp .env.example .env
```

4. Run the application:
```bash
make run
```

The API will be available at `http://localhost:8080`

### Using Docker

1. Build and run with docker-compose:
```bash
make docker-compose-up
```

2. The API will be available at `http://localhost:8080`

## API Endpoints

### Health Check
- `GET /health` - Health check endpoint

### User Management
- `GET /api/v1/users` - Get all users
- `POST /api/v1/users` - Create a new user
- `GET /api/v1/users/:id` - Get user by ID
- `PUT /api/v1/users/:id` - Update user
- `DELETE /api/v1/users/:id` - Delete user

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `PORT` | Server port | `8080` |
| `ENVIRONMENT` | Environment (development/production) | `development` |
| `LOG_LEVEL` | Log level (debug/info/warn/error) | `info` |
| `DB_HOST` | Database host | `localhost` |
| `DB_PORT` | Database port | `5432` |
| `DB_USERNAME` | Database username | `postgres` |
| `DB_PASSWORD` | Database password | `` |
| `DB_DATABASE` | Database name | `golang_app` |
| `DB_SSL_MODE` | Database SSL mode | `disable` |

## Development Commands

```bash
# Install dependencies
make deps

# Format code
make fmt

# Run tests
make test

# Run tests with coverage
make test-coverage

# Build application
make build

# Run application
make run

# Run with hot reload
make dev

# Run linter
make lint

# Run security scan
make security

# Install development tools
make install-tools

# Build Docker image
make docker-build

# Run with docker-compose
make docker-compose-up
```

## Testing

Run tests with:
```bash
make test
```

Generate coverage report:
```bash
make test-coverage
```

## Contributing

1. Fork the repository
2. Create a feature branch: `git checkout -b feature-name`
3. Make changes and add tests
4. Run tests: `make test`
5. Format code: `make fmt`
6. Commit changes: `git commit -am 'Add feature'`
7. Push to branch: `git push origin feature-name`
8. Submit a pull request

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## Architecture

This template follows clean architecture principles:

- **Handlers**: Handle HTTP requests and responses
- **Services**: Contain business logic
- **Repository**: Handle data persistence
- **Models**: Define data structures
- **Middleware**: Handle cross-cutting concerns

## Deployment

### Docker

Build and run with Docker:
```bash
docker build -t golang-app .
docker run -p 8080:8080 golang-app
```
.gitignore
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
main
golang-app

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
coverage.html
coverage.out

# Dependency directories (remove the comment below to include it)
vendor/

# Go workspace file
go.work

# Environment files
.env
.env.local
.env.development
.env.test
.env.production

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Logs
*.log
logs/

# Runtime data
pids
*.pid
*.seed

# Coverage directory used by tools like istanbul
coverage/

# Temporary directories
tmp/
temp/

# Build directories
build/
dist/

# Docker
.dockerignore

# Air (hot reload) temporary files
tmp/

# Database files
*.db
*.sqlite
*.sqlite3

# Backup files
*.backup
*.bak

# Certificate files
*.pem
*.key
*.crt
.air.toml
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ."
  delay = 0
  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"]
  include_file = []
  kill_delay = "0s"
  log = "build-errors.log"
  poll = false
  poll_interval = 0
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_root = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true
scripts/build.sh
#!/bin/bash

set -e

# Build script for Golang Web API

APP_NAME="golang-app"
VERSION=${VERSION:-"1.0.0"}
BUILD_DIR="build"
BINARY_NAME="main"

echo "Building $APP_NAME v$VERSION..."

# Create build directory
mkdir -p $BUILD_DIR

# Get build info
BUILD_TIME=$(date -u '+%Y-%m-%d_%I:%M:%S%p')
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
GO_VERSION=$(go version | awk '{print $3}')

# Build flags
LDFLAGS="-X main.Version=$VERSION -X main.BuildTime=$BUILD_TIME -X main.GitCommit=$GIT_COMMIT -X main.GoVersion=$GO_VERSION"

# Build for current platform
echo "Building for current platform..."
CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/$BINARY_NAME

# Build for multiple platforms if requested
if [ "$1" = "cross" ]; then
    echo "Cross-compiling for multiple platforms..."

    # Linux AMD64
    echo "Building for Linux AMD64..."
    GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/${BINARY_NAME}-linux-amd64

    # Linux ARM64
    echo "Building for Linux ARM64..."
    GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/${BINARY_NAME}-linux-arm64

    # macOS AMD64
    echo "Building for macOS AMD64..."
    GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/${BINARY_NAME}-darwin-amd64

    # macOS ARM64
    echo "Building for macOS ARM64..."
    GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/${BINARY_NAME}-darwin-arm64

    # Windows AMD64
    echo "Building for Windows AMD64..."
    GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$LDFLAGS" -o $BUILD_DIR/${BINARY_NAME}-windows-amd64.exe
fi

echo "Build complete! Binaries are in the $BUILD_DIR directory."
scripts/deploy.sh
#!/bin/bash

set -e

# Deployment script for Golang Web API

APP_NAME="golang-app"
VERSION=${VERSION:-"latest"}
REGISTRY=${REGISTRY:-"your-registry.com"}
ENVIRONMENT=${ENVIRONMENT:-"staging"}

echo "Deploying $APP_NAME:$VERSION to $ENVIRONMENT..."

# Build Docker image
echo "Building Docker image..."
docker build -t $REGISTRY/$APP_NAME:$VERSION .
docker tag $REGISTRY/$APP_NAME:$VERSION $REGISTRY/$APP_NAME:latest

# Push to registry
echo "Pushing to registry..."
docker push $REGISTRY/$APP_NAME:$VERSION
docker push $REGISTRY/$APP_NAME:latest

# Deploy based on environment
case $ENVIRONMENT in
    "development"|"dev")
        echo "Deploying to development environment..."
        docker-compose -f docker-compose.dev.yml up -d
        ;;
    "staging")
        echo "Deploying to staging environment..."
        kubectl apply -f k8s/staging/
        kubectl set image deployment/$APP_NAME $APP_NAME=$REGISTRY/$APP_NAME:$VERSION -n staging
        ;;
    "production"|"prod")
        echo "Deploying to production environment..."
        kubectl apply -f k8s/production/
        kubectl set image deployment/$APP_NAME $APP_NAME=$REGISTRY/$APP_NAME:$VERSION -n production
        ;;
    *)
        echo "Unknown environment: $ENVIRONMENT"
        exit 1
        ;;
esac

echo "Deployment complete!"
internal/handlers/handlers_test.go
package handlers

import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/gin-gonic/gin"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "golang-app/internal/models"
    "golang-app/internal/service"
)

// MockUserService is a mock implementation of UserService
type MockUserService struct {
    mock.Mock
}

func (m *MockUserService) CreateUser(req models.CreateUserRequest) (*models.User, error) {
    args := m.Called(req)
    return args.Get(0).(*models.User), args.Error(1)
}

func (m *MockUserService) GetUser(id string) (*models.User, error) {
    args := m.Called(id)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*models.User), args.Error(1)
}

func (m *MockUserService) GetAllUsers() ([]*models.User, error) {
    args := m.Called()
    return args.Get(0).([]*models.User), args.Error(1)
}

func (m *MockUserService) UpdateUser(id string, req models.UpdateUserRequest) (*models.User, error) {
    args := m.Called(id, req)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*models.User), args.Error(1)
}

func (m *MockUserService) DeleteUser(id string) error {
    args := m.Called(id)
    return args.Error(0)
}

func setupRouter() *gin.Engine {
    gin.SetMode(gin.TestMode)
    return gin.New()
}

func TestHealthCheck(t *testing.T) {
    router := setupRouter()
    mockService := new(MockUserService)
    handler := New(mockService)

    router.GET("/health", handler.HealthCheck)

    req, _ := http.NewRequest("GET", "/health", nil)
    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusOK, w.Code)

    var response map[string]interface{}
    err := json.Unmarshal(w.Body.Bytes(), &response)
    assert.NoError(t, err)
    assert.Equal(t, "healthy", response["status"])
    assert.Equal(t, "golang-app", response["service"])
}

func TestCreateUser(t *testing.T) {
    router := setupRouter()
    mockService := new(MockUserService)
    handler := New(mockService)

    router.POST("/users", handler.CreateUser)

    // Test successful creation
    user := &models.User{
        ID:        "123",
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    mockService.On("CreateUser", mock.AnythingOfType("models.CreateUserRequest")).Return(user, nil)

    reqBody := models.CreateUserRequest{
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    jsonBody, _ := json.Marshal(reqBody)
    req, _ := http.NewRequest("POST", "/users", bytes.NewBuffer(jsonBody))
    req.Header.Set("Content-Type", "application/json")
    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusCreated, w.Code)
    mockService.AssertExpectations(t)
}

func TestGetUser(t *testing.T) {
    router := setupRouter()
    mockService := new(MockUserService)
    handler := New(mockService)

    router.GET("/users/:id", handler.GetUser)

    // Test successful retrieval
    user := &models.User{
        ID:        "123",
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    mockService.On("GetUser", "123").Return(user, nil)

    req, _ := http.NewRequest("GET", "/users/123", nil)
    w := httptest.NewRecorder()
    router.ServeHTTP(w, req)

    assert.Equal(t, http.StatusOK, w.Code)
    mockService.AssertExpectations(t)
}
internal/service/user_service_test.go
package service

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
    "golang-app/internal/models"
    "golang-app/internal/repository"
)

// MockUserRepository is a mock implementation of UserRepository
type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) Create(user *models.User) error {
    args := m.Called(user)
    return args.Error(0)
}

func (m *MockUserRepository) GetByID(id string) (*models.User, error) {
    args := m.Called(id)
    if args.Get(0) == nil {
        return nil, args.Error(1)
    }
    return args.Get(0).(*models.User), args.Error(1)
}

func (m *MockUserRepository) GetAll() ([]*models.User, error) {
    args := m.Called()
    return args.Get(0).([]*models.User), args.Error(1)
}

func (m *MockUserRepository) Update(user *models.User) error {
    args := m.Called(user)
    return args.Error(0)
}

func (m *MockUserRepository) Delete(id string) error {
    args := m.Called(id)
    return args.Error(0)
}

func (m *MockUserRepository) ExistsByEmail(email string) bool {
    args := m.Called(email)
    return args.Bool(0)
}

func TestCreateUser(t *testing.T) {
    mockRepo := new(MockUserRepository)
    service := New(mockRepo)

    req := models.CreateUserRequest{
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    // Test successful creation
    mockRepo.On("ExistsByEmail", req.Email).Return(false)
    mockRepo.On("Create", mock.AnythingOfType("*models.User")).Return(nil)

    user, err := service.CreateUser(req)

    assert.NoError(t, err)
    assert.NotNil(t, user)
    assert.Equal(t, req.FirstName, user.FirstName)
    assert.Equal(t, req.Email, user.Email)
    mockRepo.AssertExpectations(t)
}

func TestCreateUser_DuplicateEmail(t *testing.T) {
    mockRepo := new(MockUserRepository)
    service := New(mockRepo)

    req := models.CreateUserRequest{
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    // Test duplicate email
    mockRepo.On("ExistsByEmail", req.Email).Return(true)

    user, err := service.CreateUser(req)

    assert.Error(t, err)
    assert.Nil(t, user)
    assert.Equal(t, ErrDuplicateEmail, err)
    mockRepo.AssertExpectations(t)
}

func TestGetUser(t *testing.T) {
    mockRepo := new(MockUserRepository)
    service := New(mockRepo)

    expectedUser := &models.User{
        ID:        "123",
        FirstName: "John",
        LastName:  "Doe",
        Email:     "john.doe@example.com",
        Age:       25,
    }

    // Test successful retrieval
    mockRepo.On("GetByID", "123").Return(expectedUser, nil)

    user, err := service.GetUser("123")

    assert.NoError(t, err)
    assert.NotNil(t, user)
    assert.Equal(t, expectedUser.ID, user.ID)
    assert.Equal(t, expectedUser.Email, user.Email)
    mockRepo.AssertExpectations(t)
}

func TestGetUser_NotFound(t *testing.T) {
    mockRepo := new(MockUserRepository)
    service := New(mockRepo)

    // Test user not found
    mockRepo.On("GetByID", "nonexistent").Return(nil, repository.ErrUserNotFound)

    user, err := service.GetUser("nonexistent")

    assert.Error(t, err)
    assert.Nil(t, user)
    assert.Equal(t, repository.ErrUserNotFound, err)
    mockRepo.AssertExpectations(t)
}
k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: golang-app
  labels:
    app: golang-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: golang-app
  template:
    metadata:
      labels:
        app: golang-app
    spec:
      containers:
      - name: golang-app
        image: golang-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: PORT
          value: "8080"
        - name: ENVIRONMENT
          value: "production"
        - name: LOG_LEVEL
          value: "info"
        - name: DB_HOST
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-host
        - name: DB_PORT
          value: "5432"
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-password
        - name: DB_DATABASE
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-database
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: golang-app-service
spec:
  selector:
    app: golang-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer

---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  db-host: "postgres-service"
  db-username: "golang_user"
  db-password: "your-secret-password"
  db-database: "golang_app"

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: golang-app-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - api.yourdomain.com
    secretName: golang-app-tls
  rules:
  - host: api.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: golang-app-service
            port:
              number: 80
.github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  GO_VERSION: 1.23
  DOCKER_IMAGE: golang-app

jobs:
  test:
    name: Test
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}

    - name: Cache Go modules
      uses: actions/cache@v3
      with:
        path: ~/go/pkg/mod
        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
          ${{ runner.os }}-go-

    - name: Install dependencies
      run: go mod download

    - name: Run tests
      run: go test -v -race -coverprofile=coverage.out ./...

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.out

  lint:
    name: Lint
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: ${{ env.GO_VERSION }}

    - name: Run golangci-lint
      uses: golangci/golangci-lint-action@v3
      with:
        version: latest

  security:
    name: Security Scan
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Run Gosec Security Scanner
      uses: securecodewarrior/github-action-gosec@master
      with:
        args: './...'

  build:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    needs: [test, lint, security]
    if: github.ref == 'refs/heads/main'

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Login to Docker Hub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.DOCKER_IMAGE }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=sha,prefix={{branch}}-
          type=raw,value=latest,enable={{is_default_branch}}

    - name: Build and push Docker image
      uses: docker/build-push-action@v5
      with:
        context: .
        platforms: linux/amd64,linux/arm64
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max

  deploy:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [build]
    if: github.ref == 'refs/heads/main'
    environment: production

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Deploy to Kubernetes
      run: |
        # Add your deployment script here
        echo "Deploying to production..."
        # kubectl apply -f k8s/
api-spec.json
{
  "openapi": "3.0.0",
  "info": {
    "title": "Golang Web API",
    "description": "A production-ready REST API built with Go",
    "version": "1.0.0",
    "contact": {
      "name": "API Support",
      "email": "support@example.com"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "http://localhost:8080",
      "description": "Development server"
    },
    {
      "url": "https://api.yourdomain.com",
      "description": "Production server"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check endpoint",
        "tags": ["Health"],
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "healthy"
                    },
                    "service": {
                      "type": "string",
                      "example": "golang-app"
                    },
                    "version": {
                      "type": "string",
                      "example": "1.0.0"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users": {
      "get": {
        "summary": "Get all users",
        "tags": ["Users"],
        "responses": {
          "200": {
            "description": "List of users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a new user",
        "tags": ["Users"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "User created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    },
                    "message": {
                      "type": "string",
                      "example": "User created successfully"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Email already exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users/{id}": {
      "get": {
        "summary": "Get user by ID",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User details",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Update user",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUserRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/User"
                    },
                    "message": {
                      "type": "string",
                      "example": "User updated successfully"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete user",
        "tags": ["Users"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "User deleted successfully"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "first_name": {
            "type": "string",
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "example": "Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "john.doe@example.com"
          },
          "age": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "example": 25
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2025-01-01T00:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2025-01-01T00:00:00Z"
          }
        },
        "required": ["id", "first_name", "last_name", "email", "age", "created_at", "updated_at"]
      },
      "CreateUserRequest": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 50,
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 50,
            "example": "Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "john.doe@example.com"
          },
          "age": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "example": 25
          }
        },
        "required": ["first_name", "last_name", "email", "age"]
      },
      "UpdateUserRequest": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 50,
            "example": "John"
          },
          "last_name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 50,
            "example": "Doe"
          },
          "email": {
            "type": "string",
            "format": "email",
            "example": "john.doe@example.com"
          },
          "age": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "example": 25
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "validation_error"
          },
          "message": {
            "type": "string",
            "example": "Invalid request data"
          }
        },
        "required": ["error"]
      }
    }
  }
}
Common Button Mistake in UI/UX
Next