15 Go Prompts for Microservices, APIs, and CLI Tools

Introduction

Go (Golang) has become the language of choice for building high-performance microservices, robust APIs, and efficient command-line tools. Its simplicity, built-in concurrency, and fast compilation make it ideal for production-grade systems. But even experienced Go developers spend time on repetitive tasks: writing boilerplate for HTTP handlers, structuring microservices, or generating CLI commands. This is where AI prompts come in.

By using carefully crafted prompts, you can turn AI into your pair programmer — generating code skeletons, debugging race conditions, or optimizing SQL queries. Below is a collection of 15 battle-tested prompts I use daily in my Go workflow. Each prompt includes a real-world example and a tip to get the most out of it.

1. Generate a REST API Handler with Error Handling

Prompt:

"Write a Go HTTP handler for a REST endpoint POST /api/v1/users that creates a new user. Use the standard net/http package, parse JSON request body, validate required fields (name, email), and return proper HTTP status codes (201, 400, 500). Include structured logging."

Usage example:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "regexp"
)

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var req CreateUserRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        log.Printf("Error decoding request: %v", err)
        http.Error(w, "Invalid JSON", http.StatusBadRequest)
        return
    }

    if req.Name == "" || req.Email == "" {
        http.Error(w, "Name and email are required", http.StatusBadRequest)
        return
    }

    emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    if !emailRegex.MatchString(req.Email) {
        http.Error(w, "Invalid email format", http.StatusBadRequest)
        return
    }

    // Simulate user creation
    log.Printf("Creating user: %s (%s)", req.Name, req.Email)
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(map[string]string{"status": "created"})
}

Tip: Add context about your logging library (e.g., log/slog in Go 1.21+) to get more idiomatic code.

2. Generate a Go Microservice Scaffold

Prompt:

"Generate a Go project structure for a microservice with the following: cmd/server/main.go, internal/handler, internal/service, internal/repository, internal/model, pkg/config, go.mod. Include dependency injection using constructor functions, environment-based config via os.Getenv, and graceful shutdown."

Usage example (output structure):

my-service/
├── cmd/
   └── server/
       └── main.go
├── internal/
   ├── handler/
      └── user.go
   ├── service/
      └── user.go
   ├── repository/
      └── user.go
   └── model/
       └── user.go
├── pkg/
   └── config/
       └── config.go
├── go.mod
└── go.sum

Tip: Specify the database (e.g., PostgreSQL) and framework (e.g., Chi or Gin) to get more tailored code.

3. Write a CLI Tool with Cobra

Prompt:

"Create a Go CLI tool using the Cobra library that has commands: init (creates a config file), run (starts a server), and version (prints version). Use Viper for config management. Include a root command with persistent flags for --config and --verbose."

Usage example (main.go):

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var rootCmd = &cobra.Command{
    Use:   "mycli",
    Short: "MyCLI is a sample CLI tool",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Use 'mycli --help' for usage")
    },
}

var initCmd = &cobra.Command{
    Use:   "init",
    Short: "Initialize config file",
    Run: func(cmd *cobra.Command, args []string) {
        viper.SetConfigName("config")
        viper.AddConfigPath(".")
        viper.SafeWriteConfig()
        fmt.Println("Config file created")
    },
}

func main() {
    rootCmd.AddCommand(initCmd)
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

Tip: Ask AI to include subcommands with flags (e.g., --output) for more complex tools.

4. Generate a gRPC Server and Client

Prompt:

"Write Go code for a gRPC server and client for a UserService with RPCs: CreateUser, GetUser, ListUsers. Use protocol buffers with fields: id (int32), name (string), email (string). Implement server-side logging and client-side error handling."

Usage example (protobuf):

syntax = "proto3";

package user;

service UserService {
    rpc CreateUser (CreateUserRequest) returns (User);
    rpc GetUser (GetUserRequest) returns (User);
    rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}

message User {
    int32 id = 1;
    string name = 2;
    string email = 3;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
}

message GetUserRequest {
    int32 id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 page_size = 2;
}

message ListUsersResponse {
    repeated User users = 1;
}

Tip: Ask AI to add interceptors for metrics (e.g., Prometheus) or authentication.

5. Optimize SQL Queries with GORM

Prompt:

"Given a GORM model User with fields ID, Name, Email, CreatedAt, and a Post model with ID, Title, Body, UserID, write an optimized query to fetch all users with their latest post. Use preloading and avoid N+1 queries. Include indexing recommendation."

Usage example:

package main

import (
    "gorm.io/gorm"
    "time"
)

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string
    Email     string
    CreatedAt time.Time
    Posts     []Post    `gorm:"foreignKey:UserID"`
}

type Post struct {
    ID        uint   `gorm:"primaryKey"`
    Title     string
    Body      string
    UserID    uint
    CreatedAt time.Time
}

func GetUsersWithLatestPost(db *gorm.DB) ([]User, error) {
    var users []User
    err := db.
        Preload("Posts", func(db *gorm.DB) *gorm.DB {
            return db.Order("created_at DESC").Limit(1)
        }).
        Find(&users).Error
    return users, err
}

Tip: Include EXPLAIN output in the prompt for AI to suggest indexes.

6. Write a Concurrent Worker Pool

Prompt:

"Implement a worker pool in Go using goroutines and channels. The pool should accept jobs (type Job with ID and Payload), process them with a configurable number of workers, and return results via a results channel. Include graceful shutdown with context cancellation."

Usage example:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type Job struct {
    ID      int
    Payload string
}

type Result struct {
    JobID  int
    Output string
}

func WorkerPool(ctx context.Context, jobs <-chan Job, results chan<- Result, numWorkers int) {
    var wg sync.WaitGroup
    for i := 0; i < numWorkers; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for {
                select {
                case <-ctx.Done():
                    return
                case job, ok := <-jobs:
                    if !ok {
                        return
                    }
                    // Simulate work
                    time.Sleep(100 * time.Millisecond)
                    results <- Result{JobID: job.ID, Output: fmt.Sprintf("Processed by worker %d", id)}
                }
            }
        }(i)
    }
    wg.Wait()
    close(results)
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    jobs := make(chan Job, 10)
    results := make(chan Result, 10)

    // Start workers
    go WorkerPool(ctx, jobs, results, 3)

    // Send jobs
    go func() {
        for i := 0; i < 10; i++ {
            jobs <- Job{ID: i, Payload: fmt.Sprintf("data-%d", i)}
        }
        close(jobs)
    }()

    // Collect results
    for res := range results {
        fmt.Printf("Job %d: %s\n", res.JobID, res.Output)
    }
}

Tip: Ask for error handling and retry logic for production use.

7. Debug a Race Condition

Prompt:

"Analyze this Go code for race conditions: [paste code]. Suggest fixes using mutexes, atomic operations, or channels. Explain the root cause and show the corrected version."

Usage example (common race):

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *Counter) Value() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.value
}

func main() {
    var wg sync.WaitGroup
    counter := &Counter{}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }
    wg.Wait()
    fmt.Println(counter.Value())
}

Tip: Use go run -race output in your prompt for AI to pinpoint exact lines.

8. Generate a Middleware Chain

Prompt:

"Create a chain of HTTP middlewares in Go for logging, recovery, and CORS. Use the standard net/http handler pattern. Each middleware should wrap the next and log request duration."

Usage example:

package main

import (
    "log"
    "net/http"
    "time"
)

func LoggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func RecoveryMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
                log.Printf("Panic: %v", err)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func CORSMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "*")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusOK)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    handler := LoggingMiddleware(RecoveryMiddleware(CORSMiddleware(mux)))
    http.ListenAndServe(":8080", handler)
}

Tip: Ask for third-party middleware libraries (e.g., goji) if you need more features.

9. Write a Unit Test with Table-Driven Tests

Prompt:

"Write a Go unit test for a function func Add(a, b int) int. Use table-driven tests with sub-tests. Include edge cases like negative numbers and zero. Also write a benchmark function."

Usage example:

package main

import (
    "testing"
)

func Add(a, b int) int {
    return a + b
}

func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 5, 5},
        {"mixed", -1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(1, 2)
    }
}

Tip: Ask for mock generation using gomock or testify for interfaces.

10. Implement a Simple Rate Limiter

Prompt:

"Write a token bucket rate limiter in Go using sync.Mutex and time.Ticker. It should allow N requests per second with a configurable burst. Return HTTP 429 when limit exceeded."

Usage example:

package main

import (
    "net/http"
    "sync"
    "time"
)

type TokenBucket struct {
    mu         sync.Mutex
    tokens     float64
    maxTokens  float64
    refillRate float64
    lastRefill time.Time
}

func NewTokenBucket(rate float64, burst int) *TokenBucket {
    return &TokenBucket{
        tokens:     float64(burst),
        maxTokens:  float64(burst),
        refillRate: rate,
        lastRefill: time.Now(),
    }
}

func (tb *TokenBucket) Allow() bool {
    tb.mu.Lock()
    defer tb.mu.Unlock()

    now := time.Now()
    elapsed := now.Sub(tb.lastRefill).Seconds()
    tb.tokens += elapsed * tb.refillRate
    if tb.tokens > tb.maxTokens {
        tb.tokens = tb.maxTokens
    }
    tb.lastRefill = now

    if tb.tokens >= 1 {
        tb.tokens--
        return true
    }
    return false
}

func RateLimitMiddleware(tb *TokenBucket) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !tb.Allow() {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

Tip: AI can also generate Redis-based rate limiter if you specify a distributed environment.

11. Generate a JSON Web Token (JWT) Middleware

Prompt:

"Write a Go middleware that validates JWT tokens from the Authorization header. Use the golang-jwt/jwt library. Extract claims (user_id, role) and set them in the request context. Return 401 for invalid or expired tokens."

Usage example:

package main

import (
    "context"
    "net/http"
    "strings"

    "github.com/golang-jwt/jwt/v5"
)

type contextKey string

const UserIDKey contextKey = "user_id"
const RoleKey contextKey = "role"

func AuthMiddleware(jwtSecret string) func(http.Handler) http.Handler {
    return func(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 token", http.StatusUnauthorized)
                return
            }

            tokenString := strings.TrimPrefix(authHeader, "Bearer ")
            token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
                return []byte(jwtSecret), nil
            })

            if err != nil || !token.Valid {
                http.Error(w, "Invalid token", http.StatusUnauthorized)
                return
            }

            claims, ok := token.Claims.(jwt.MapClaims)
            if !ok {
                http.Error(w, "Invalid claims", http.StatusUnauthorized)
                return
            }

            ctx := context.WithValue(r.Context(), UserIDKey, claims["user_id"])
            ctx = context.WithValue(ctx, RoleKey, claims["role"])
            next.ServeHTTP(w, r.WithContext(ctx))
        })
    }
}

Tip: Ask AI to include refresh token logic or integration with a user service.

12. Write a SQL Migration Script with Goose

Prompt:

"Generate a Goose migration file in Go that creates a users table with columns: id (serial primary key), name (varchar 255), email (varchar 255 unique), created_at (timestamp). Include an Up function and a Down function. Use the github.com/pressly/goose library."

Usage example (migration file):

package migrations

import (
    "database/sql"
    "github.com/pressly/goose/v3"
)

func init() {
    goose.AddMigration(Up, Down)
}

func Up(tx *sql.Tx) error {
    _, err := tx.Exec(`
        CREATE TABLE users (
            id SERIAL PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            email VARCHAR(255) UNIQUE NOT NULL,
            created_at TIMESTAMP DEFAULT NOW()
        );
    `)
    return err
}

func Down(tx *sql.Tx) error {
    _, err := tx.Exec("DROP TABLE IF EXISTS users;")
    return err
}

Tip: Ask for seed data or foreign key constraints in the same migration.

13. Generate a Prometheus Metrics Endpoint

Prompt:

"Add Prometheus metrics to a Go HTTP server using prometheus/client_golang. Include counters for total requests, request duration histogram, and gauge for active connections. Register metrics and expose /metrics endpoint."

Usage example:

package main

import (
    "net/http"
    "time"

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

var (
    totalRequests = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "path"},
    )
    activeConnections = prometheus.NewGauge(
        prometheus.GaugeOpts{
            Name: "http_active_connections",
            Help: "Number of active HTTP connections",
        },
    )
)

func init() {
    prometheus.MustRegister(totalRequests, requestDuration, activeConnections)
}

func MetricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        activeConnections.Inc()
        defer activeConnections.Dec()

        start := time.Now()
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        next.ServeHTTP(wrapped, r)
        duration := time.Since(start).Seconds()

        totalRequests.WithLabelValues(r.Method, r.URL.Path, http.StatusText(wrapped.statusCode)).Inc()
        requestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
    })
}

type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

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

func main() {
    mux := http.NewServeMux()
    mux.Handle("/metrics", promhttp.Handler())
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    http.ListenAndServe(":8080", MetricsMiddleware(mux))
}

Tip: Specify which metrics you care about (e.g., error rate) to get focused code.

14. Implement a Graceful Shutdown Pattern

Prompt:

"Write a Go program that starts an HTTP server and shuts down gracefully on SIGINT/SIGTERM. Use os/signal and context.WithTimeout. Log when server starts and stops."

Usage example:

package main

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

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello, World!"))
    })

    server := &http.Server{
        Addr:    ":8080",
        Handler: mux,
    }

    go func() {
        log.Println("Server starting on :8080")
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("Server error: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("Shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Fatalf("Server forced to shutdown: %v", err)
    }
    log.Println("Server exited")
}

Tip: Add cleanup for database connections or file handles in the shutdown.

15. Generate a Dockerfile and Docker Compose for a Go App

Prompt:

"Write a multi-stage Dockerfile for a Go application that builds the binary in one stage (using golang:1.22-alpine) and runs it in a smaller image (alpine:3.19). Also create a docker-compose.yml with the Go app and a PostgreSQL database."

Usage example (Dockerfile):

# Build stage
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server

# Run stage
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]

docker-compose.yml:

version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
      - DB_USER=postgres
      - DB_PASSWORD=postgres
      - DB_NAME=mydb
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: mydb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Tip: Ask for environment-specific overrides (e.g., docker-compose.prod.yml).

Conclusion

These 15 prompts cover the most common tasks in Go development — from scaffolding microservices to deploying with Docker. By using AI as a coding assistant, you can reduce boilerplate time by 50-70% and focus on business logic. The key is to be specific: include the library names, error handling patterns, and context about your system. Go's simplicity combined with AI's speed makes for a powerful duo. Start with one prompt today, adapt it to your project, and watch your productivity soar.

← All posts

Comments