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.
Comments