18 Prompts for Go: Microservices, APIs, and CLI Utilities
As a developer who uses AI daily to speed up work, I’ve compiled a battle-tested collection of prompts for Go development. These are not theoretical; they come from real projects building microservices, REST and gRPC APIs, and command-line tools. Each prompt is complete with a usage example and explanation, so you can copy-paste and adapt. Let’s dive in.
Why These Prompts?
Go’s simplicity and performance make it ideal for backend services and CLI apps. But writing idiomatic Go—with proper error handling, concurrency patterns, and testing—takes experience. These prompts help you generate code that follows Go best practices, from project scaffolding to deployment. They save hours of boilerplate and debugging.
The Prompts
1. Scaffold a New Microservice Project
Prompt: "Create a Go microservice project structure with Gin, GORM, PostgreSQL, and Docker. Include a Makefile with targets for build, test, lint, and run. Use environment variables for configuration. Organize into cmd/, internal/, and pkg/ directories."
Example:
microservice/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── handler/
│ │ └── user.go
│ ├── model/
│ │ └── user.go
│ ├── repository/
│ │ └── user.go
│ └── service/
│ └── user.go
├── pkg/
│ └── database/
│ └── postgres.go
├── Dockerfile
├── Makefile
├── go.mod
└── go.sum
Explanation: This prompt sets up a clean architecture with separation of concerns. The internal/ directory prevents external packages from importing your application code, following Go conventions. Use this for any new microservice to enforce consistency.
2. Generate a RESTful CRUD API Handler
Prompt: "Write a Gin handler for CRUD operations on a 'Product' resource. Use GORM for database access. Include input validation with go-playground/validator, proper HTTP status codes, and error responses in JSON format. Handle pagination, filtering by category, and sorting by price."
Example output (handler snippet):
func GetProducts(c *gin.Context) {
var req models.ProductQuery
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
var products []models.Product
query := db.Model(&models.Product{})
if req.Category != "" {
query = query.Where("category = ?", req.Category)
}
query = query.Order(req.SortBy).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize)
if err := query.Find(&products).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
return
}
c.JSON(http.StatusOK, products)
}
Explanation: This prompt ensures you get production-ready code with validation and pagination. It follows RESTful conventions and returns meaningful error messages.
3. Implement a gRPC Service with Protobuf
Prompt: "Define a protobuf service 'UserService' with methods: GetUser, CreateUser, UpdateUser, DeleteUser. Generate Go server and client code using protoc. Implement the server with logging middleware, context cancellation, and error handling with gRPC status codes."
Example proto:
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc CreateUser (CreateUserRequest) returns (User);
rpc UpdateUser (UpdateUserRequest) returns (User);
rpc DeleteUser (DeleteUserRequest) returns (google.protobuf.Empty);
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}
Explanation: gRPC is efficient for inter-service communication. This prompt gives you a complete contract and implementation pattern. Use it when building microservices that need high throughput.
4. Write a CLI Tool with Cobra
Prompt: "Create a CLI app named 'taskctl' using Cobra and Viper. Support subcommands: add, list, complete, delete. Use a local SQLite database with GORM. Store tasks with title, description, due date, and status. Implement flags for list filtering (status, due before)."
Example command:
taskctl add --title "Finish report" --due 2026-08-01
taskctl list --status pending
taskctl complete 1
taskctl delete 1
Explanation: Cobra is the standard for Go CLIs. This prompt scaffolds a complete app with persistence. It’s a template you can reuse for any CLI tool, from data processing to DevOps utilities.
5. Add Structured Logging with slog
Prompt: "Integrate Go's log/slog for structured logging in a microservice. Set up JSON output, log level from environment, request ID middleware, and custom handler that includes service name and version. Replace all fmt.Print calls with slog."
Example:
log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
slog.SetDefault(log)
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = uuid.NewString()
}
c.Set("request_id", requestID)
slog.Info("request started", "method", c.Request.Method, "path", c.Request.URL.Path, "request_id", requestID)
c.Next()
}
}
Explanation: Structured logging is critical for debugging microservices. slog is part of Go 1.21+ and is more performant than third-party libraries. This pattern makes logs machine-readable and searchable.
6. Implement Graceful Shutdown
Prompt: "Add graceful shutdown to a Gin server listening on signals SIGINT and SIGTERM. Include a timeout of 30 seconds for active requests to complete. Log shutdown initiation and completion."
Example:
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", 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 := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
Explanation: Graceful shutdown prevents data loss and ensures in-flight requests complete. This prompt is essential for production services.
7. Write Unit Tests with Testify
Prompt: "Write unit tests for a service layer using Testify suite and mocks. Test CreateUser, GetUser, and DeleteUser. Mock the repository interface. Cover success, not found, and duplicate email cases. Use table-driven tests."
Example:
func (s *UserServiceTestSuite) TestCreateUser() {
tests := []struct {
name string
input models.User
mockErr error
wantErr bool
}{
{"success", models.User{Email: "test@test.com"}, nil, false},
{"duplicate email", models.User{Email: "dup@test.com"}, gorm.ErrDuplicatedKey, true},
}
for _, tt := range tests {
s.Run(tt.name, func() {
s.mockRepo.On("Create", mock.Anything, tt.input).Return(tt.mockErr)
err := s.service.CreateUser(context.Background(), tt.input)
assert.Equal(s.T(), tt.wantErr, err != nil)
})
}
}
Explanation: Testify simplifies mocking and assertions. Table-driven tests are idiomatic in Go and make it easy to add new test cases.
8. Generate OpenAPI/Swagger Documentation
Prompt: "Add swaggo/swag annotations to a Gin handler for a user API. Include request/response models, example values, and authentication header. Generate swagger.json and serve docs at /swagger/index.html."
Example annotation:
// GetUser godoc
// @Summary Get user by ID
// @Description Get a single user by their UUID
// @Tags users
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} models.User
// @Failure 404 {object} models.ErrorResponse
// @Router /users/{id} [get]
// @Security ApiKeyAuth
func GetUser(c *gin.Context) {
// ...
}
Explanation: Auto-generated docs keep API documentation in sync with code. Swaggo parses annotations and produces OpenAPI 2.0 spec.
9. Implement Middleware for Rate Limiting
Prompt: "Write a Gin middleware for rate limiting per IP using the token bucket algorithm. Allow 100 requests per minute. Return 429 Too Many Requests with Retry-After header and JSON error body. Use sync.Map for concurrency safety."
Example:
func RateLimiter(rate int, burst int) gin.HandlerFunc {
limiter := rate.NewLimiter(rate.Limit(rate), burst)
return func(c *gin.Context) {
if !limiter.Allow() {
c.Header("Retry-After", "60")
c.JSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"})
c.Abort()
return
}
c.Next()
}
}
Explanation: Rate limiting protects your API from abuse. The token bucket algorithm is simple and effective. Use golang.org/x/time/rate.
10. Build a Worker Pool for Background Jobs
Prompt: "Create a worker pool pattern in Go to process jobs from a channel. Use 10 workers that consume jobs, log progress, and handle panics gracefully with recover. Support graceful shutdown by draining the queue."
Example:
type Job struct {
ID int
Data string
}
func worker(id int, jobs <-chan Job, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
func() {
defer func() {
if r := recover(); r != nil {
slog.Error("worker panicked", "worker_id", id, "recover", r)
}
}()
slog.Info("processing job", "worker_id", id, "job_id", job.ID)
time.Sleep(time.Second) // simulate work
}()
}
}
Explanation: Worker pools are a Go concurrency classic. This pattern is ideal for processing queues, batch operations, or parallel tasks.
11. Add Prometheus Metrics Endpoint
Prompt: "Instrument a Gin server with Prometheus metrics: request count, latency histogram, and error count. Expose /metrics endpoint. Use promhttp and prometheus/client_golang. Add custom metrics for database query duration."
Example:
var (
httpRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{Name: "http_requests_total", Help: "Total HTTP requests"},
[]string{"method", "path", "status"},
)
httpDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{Name: "http_request_duration_seconds", Help: "Request duration"},
[]string{"method", "path"},
)
)
Explanation: Metrics are crucial for monitoring. This prompt sets up standard HTTP metrics that integrate with Grafana dashboards.
12. Write a Database Migration Script
Prompt: "Create a migration file using golang-migrate/migrate. Write a migration to add a 'products' table with columns id, name, price, created_at, updated_at. Include both up and down migrations. Use PostgreSQL dialect."
Example (000001_create_products_table.up.sql):
CREATE TABLE IF NOT EXISTS products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
Explanation: Version-controlled migrations keep database schema in sync across environments. golang-migrate supports many databases and CLI/Go integration.
13. Implement JWT Authentication Middleware
Prompt: "Write a Gin middleware that validates JWT tokens from the Authorization header. Use golang-jwt/jwt/v5. Parse claims for user ID and role. Return 401 for missing/invalid tokens. Skip validation for public routes."
Example:
func AuthMiddleware(jwtSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
tokenStr := c.GetHeader("Authorization")
if tokenStr == "" || !strings.HasPrefix(tokenStr, "Bearer ") {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
tokenStr = strings.TrimPrefix(tokenStr, "Bearer ")
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtSecret), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("user_id", claims.UserID)
c.Set("role", claims.Role)
c.Next()
}
}
Explanation: JWT is a stateless authentication standard. This pattern is widely used in Go APIs.
14. Create a Configuration Loader
Prompt: "Write a Go package that loads configuration from environment variables and a YAML file using Viper. Define a Config struct with Server, Database, and JWT sections. Support defaults and validation. Panic on missing required fields."
Example:
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
JWT JWTConfig `mapstructure:"jwt"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
Host string `mapstructure:"host"`
Timeout int `mapstructure:"timeout"`
}
func LoadConfig(path string) (*Config, error) {
viper.SetConfigFile(path)
viper.AutomaticEnv()
viper.SetEnvPrefix("APP")
if err := viper.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil {
return nil, err
}
// validation
if cfg.Server.Port == 0 {
return nil, errors.New("server.port is required")
}
return &cfg, nil
}
Explanation: Viper is the de facto configuration library. This pattern supports multiple sources and validation.
15. Write a Health Check Endpoint
Prompt: "Implement a health check handler that returns JSON with status 'ok' and uptime. Include a database ping check. Return 200 if healthy, 503 if database is down. Add a /readyz endpoint for Kubernetes liveness and readiness probes."
Example:
func Healthz(db *gorm.DB) gin.HandlerFunc {
startTime := time.Now()
return func(c *gin.Context) {
if err := db.Raw("SELECT 1").Error; err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"status": "unhealthy", "reason": "database down"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok", "uptime": time.Since(startTime).String()})
}
}
Explanation: Health endpoints are required for container orchestration. This pattern ensures your service can be monitored.
16. Implement Circuit Breaker Pattern
Prompt: "Use the sony/gobreaker library to implement a circuit breaker for HTTP calls to an external service. Configure failure threshold of 5, timeout of 30 seconds. Return fallback response on circuit open."
Example:
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "external-api",
MaxRequests: 5,
Interval: 0,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 5 && failureRatio >= 0.6
},
OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
slog.Warn("circuit breaker state changed", "from", from, "to", to)
},
})
body, err := cb.Execute(func() (interface{}, error) {
resp, err := http.Get("https://external-api.com/data")
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
})
Explanation: Circuit breakers prevent cascading failures in distributed systems. gobreaker is a popular library in Go ecosystem.
17. Build a Simple gRPC Interceptor for Logging
Prompt: "Write a unary gRPC interceptor that logs every request with method, duration, and error. Use slog. Add a client-side interceptor that adds tracing headers (x-request-id)."
Example:
func UnaryServerLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
slog.Info("gRPC request", "method", info.FullMethod, "duration", time.Since(start), "error", err)
return resp, err
}
Explanation: Interceptors are middleware for gRPC. This pattern adds observability without changing business logic.
18. Generate a Dockerfile for Multi-Stage Build
Prompt: "Write a multi-stage Dockerfile for a Go microservice. Use golang:1.22-alpine for build, alpine:3.19 for runtime. Copy only the binary, set non-root user, expose port 8080, use exec form of CMD. Include health check with curl."
Example:
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
FROM alpine:3.19
RUN apk --no-cache add ca-certificates curl
RUN adduser -D appuser
COPY --from=builder /app/server /server
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:8080/healthz || exit 1
CMD ["/server"]
Explanation: Multi-stage builds produce tiny images (around 10-20 MB). This is the standard for deploying Go services.
Conclusion
These 18 prompts cover the essential patterns for building Go microservices, APIs, and CLI tools. They are battle-tested in production environments and follow idiomatic Go practices. Copy them, adapt them to your specific needs, and you’ll write cleaner, more robust code faster. Start using the one that matches your current task—whether it’s scaffolding a new service or adding observability. And remember, the best AI prompts are those that combine clear instructions with domain knowledge. Happy coding in Go!
Comments