Introduction
Go, also known as Golang, has become a powerhouse for building modern backend systems. Its simplicity, built-in concurrency, and fast compilation make it the go-to choice for microservices, RESTful APIs, and command-line tools. According to the 2025 Stack Overflow Developer Survey, Go ranks among the top 10 most loved languages, with over 13% of professional developers using it regularly. Companies like Uber, Dropbox, and Twitch rely on Go for performance-critical services.
However, even experienced developers sometimes spend hours designing service structures, writing repetitive boilerplate, or debugging concurrency issues. That's where prompts come in. By using well-crafted prompts with AI assistants like ChatGPT, Claude, or GitHub Copilot, you can accelerate development, generate robust code skeletons, and get expert guidance on Go best practices.
This article provides 12 high-quality prompts organized into three categories: basic prompts for beginners, advanced prompts for production-level microservices and APIs, and expert prompts for CLI utilities and performance optimization. Each prompt includes a clear task, the exact prompt text, and a concrete example result with code you can adapt immediately.
Basic Prompts
1. Simple HTTP REST API with CRUD Operations
Task: Create a minimal HTTP REST API in Go that handles CRUD operations for a resource (e.g., books) without external frameworks.
Prompt:
Write a Go program that implements a RESTful API for managing a collection of books. Use only the standard library (net/http, encoding/json). The API should support:
- GET /books — list all books
- GET /books/{id} — get a single book by ID
- POST /books — create a new book
- PUT /books/{id} — update an existing book
- DELETE /books/{id} — delete a book
Store data in an in-memory slice protected by a mutex. Use gorilla/mux for routing? No, use the standard library's http.ServeMux. Return JSON responses with proper HTTP status codes.
Example Result (Key Snippet):
package main
import (
"encoding/json"
"net/http"
"strconv"
"sync"
)
type Book struct {
ID int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
}
var (
books = []Book{}
nextID = 1
mu sync.Mutex
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/books", handleBooks)
mux.HandleFunc("/books/", handleBookByID)
http.ListenAndServe(":8080", mux)
}
func handleBooks(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
mu.Lock()
json.NewEncoder(w).Encode(books)
mu.Unlock()
case http.MethodPost:
var b Book
json.NewDecoder(r.Body).Decode(&b)
mu.Lock()
b.ID = nextID
nextID++
books = append(books, b)
mu.Unlock()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(b)
}
}
This provides a solid foundation without third-party dependencies.
2. Go Module and Package Structure
Task: Understand how to structure a Go project with multiple packages and modules.
Prompt:
Explain how to set up a Go project with a main package and two sub-packages: 'models' and 'handlers'. Show the directory layout, go.mod file, and how to import the sub-packages from main.go. Include a simple example where models defines a User struct and handlers defines a function to print user details.
Example Result:
Directory layout:
project/
├── go.mod
├── main.go
├── models/
│ └── user.go
└── handlers/
└── user_handler.go
go.mod:
module github.com/example/project
go 1.22
models/user.go:
package models
type User struct {
Name string
Age int
}
handlers/user_handler.go:
package handlers
import (
"fmt"
"github.com/example/project/models"
)
func PrintUser(u models.User) {
fmt.Printf("User: %s, Age: %d\n", u.Name, u.Age)
}
main.go:
package main
import "github.com/example/project/handlers"
import "github.com/example/project/models"
func main() {
u := models.User{Name: "Alice", Age: 30}
handlers.PrintUser(u)
}
This structure scales well for microservices.
3. Error Handling Best Practices
Task: Write Go code demonstrating idiomatic error handling, including custom error types and wrapping.
Prompt:
Write a Go function that reads a file and returns its contents. Use proper error handling: return a custom error type that includes the filename and the underlying error. Also show how to use errors.Is and errors.As for error inspection in the caller.
Example Result:
package main
import (
"errors"
"fmt"
"os"
)
type FileError struct {
Filename string
Err error
}
func (e *FileError) Error() string {
return fmt.Sprintf("file %s: %v", e.Filename, e.Err)
}
func (e *FileError) Unwrap() error {
return e.Err
}
func ReadFile(filename string) ([]byte, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, &FileError{Filename: filename, Err: err}
}
return data, nil
}
func main() {
_, err := ReadFile("nonexistent.txt")
if err != nil {
var fe *FileError
if errors.As(err, &fe) {
fmt.Printf("Failed to read %s: %v\n", fe.Filename, fe.Err)
}
}
}
This pattern is recommended in the official Go blog post "Errors are values."
Advanced Prompts
4. Microservice with gRPC and Protobuf
Task: Build a gRPC-based microservice in Go with a unary RPC and server-side streaming.
Prompt:
Create a gRPC service in Go for a user management system. Define a .proto file with two RPCs: GetUser (unary) and ListUsers (server-streaming). Generate the Go code using protoc. Then implement the server and a simple client. Use the official gRPC-Go package (google.golang.org/grpc).
Example Result (Proto file):
syntax = "proto3";
package userpb;
message GetUserRequest {
int32 id = 1;
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message ListUsersRequest {
int32 page_size = 1;
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
The server implementation registers the service and starts listening on port 50051. This approach is used in production at companies like Lyft and Netflix for inter-service communication.
5. REST API with Gin Framework, Middleware, and JWT Auth
Task: Build a secure REST API using Gin with authentication middleware.
Prompt:
Write a Go REST API for a todo list using the Gin framework. Include:
- User registration and login endpoints
- JWT-based authentication middleware
- CRUD for todos, protected by auth
- Use PostgreSQL with the pgx driver for persistence
- Store passwords hashed with bcrypt
- Return proper error responses
Example Result (Auth Middleware):
package middleware
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
return []byte("secret"), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Next()
}
}
Gin is one of the most popular Go frameworks, with over 70k stars on GitHub.
6. Database Migrations with golang-migrate
Task: Implement database versioning and migrations using the golang-migrate library.
Prompt:
Show how to use golang-migrate/migrate to manage PostgreSQL schema changes in a Go microservice. Provide two migration files: one to create a 'users' table, and another to add an 'age' column. Then write Go code to run migrations programmatically at startup.
Example Result (Migration Files):
migrations/000001_create_users.up.sql:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
migrations/000002_add_age.up.sql:
ALTER TABLE users ADD COLUMN age INT DEFAULT 0;
Go code:
import (
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func RunMigrations(dbURL string) error {
m, err := migrate.New("file://migrations", dbURL)
if err != nil {
return err
}
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
return err
}
return nil
}
This pattern ensures schema consistency across environments.
7. Concurrency Patterns: Worker Pool and Fan-Out/Fan-In
Task: Implement a worker pool pattern in Go for processing tasks concurrently.
Prompt:
Write a Go program that implements a worker pool with a fixed number of goroutines. The main function sends job IDs to a channel, workers pick them up, simulate processing (e.g., sleep for random time), and send results to a results channel. Use sync.WaitGroup to coordinate shutdown. Also demonstrate fan-out (distributing jobs) and fan-in (collecting results).
Example Result:
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
sleep := time.Duration(rand.Intn(1000)) * time.Millisecond
time.Sleep(sleep)
results <- j * 2
fmt.Printf("Worker %d processed job %d\n", id, j)
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
for r := range results {
fmt.Printf("Result: %d\n", r)
}
}
This is a classic pattern from Go's official concurrency documentation.
Expert Prompts
8. CLI Utility with Cobra and Viper
Task: Build a professional command-line tool using Cobra and Viper for configuration.
Prompt:
Create a Go CLI tool called 'taskctl' that manages tasks (add, list, complete). Use Cobra for command structure and Viper for configuration (e.g., storage file path). Store tasks in a JSON file. Commands:
- taskctl add "Buy milk"
- taskctl list
- taskctl complete 1
- taskctl config set storage ~/tasks.json
Example Result (Cobra Command):
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var addCmd = &cobra.Command{
Use: "add [task]",
Short: "Add a new task",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
// Logic to append task to JSON file
fmt.Printf("Added task: %s\n", task)
},
}
func init() {
rootCmd.AddCommand(addCmd)
}
Cobra is used by Kubernetes, Hugo, and many other popular Go projects.
9. Performance Profiling and Benchmarking
Task: Write a Go program and profile it to identify bottlenecks.
Prompt:
Write a Go function that concatenates a large number of strings inefficiently using +, then efficiently using strings.Builder. Create benchmarks for both versions using the testing package. Then show how to run the benchmark with CPU and memory profiling enabled (go test -bench=. -cpuprofile=cpu.out -memprofile=mem.out). Explain how to view the profile with 'go tool pprof'.
Example Result (Benchmark):
package main
import (
"strings"
"testing"
)
func ConcatInefficient(n int) string {
var s string
for i := 0; i < n; i++ {
s += "a"
}
return s
}
func ConcatEfficient(n int) string {
var sb strings.Builder
for i := 0; i < n; i++ {
sb.WriteString("a")
}
return sb.String()
}
func BenchmarkConcatInefficient(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatInefficient(10000)
}
}
func BenchmarkConcatEfficient(b *testing.B) {
for i := 0; i < b.N; i++ {
ConcatEfficient(10000)
}
}
Running go test -bench=. shows the efficient version is orders of magnitude faster. Profiling helps identify such issues in real codebases.
10. Unit Testing and Test Coverage
Task: Write comprehensive unit tests for a Go package using table-driven tests and mocking.
Prompt:
Write a Go package that fetches weather data from an external API. Define an interface for the HTTP client to enable mocking. Then write table-driven unit tests that cover success, network error, and JSON parse error cases. Use the testing package and a mock HTTP client.
Example Result:
package weather
import (
"encoding/json"
"net/http"
)
type HTTPClient interface {
Get(url string) (*http.Response, error)
}
type Client struct {
httpClient HTTPClient
}
func (c *Client) GetTemperature(city string) (float64, error) {
resp, err := c.httpClient.Get("http://api.weather.com/" + city)
if err != nil {
return 0, err
}
defer resp.Body.Close()
var data struct {
Temp float64 `json:"temp"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return 0, err
}
return data.Temp, nil
}
Test:
func TestGetTemperature_Success(t *testing.T) {
mock := &MockHTTPClient{}
// Setup mock response
client := &Client{httpClient: mock}
temp, err := client.GetTemperature("London")
if err != nil || temp != 20.5 {
t.Errorf("expected 20.5, got %v, err %v", temp, err)
}
}
This approach ensures high test coverage without external dependencies.
11. Graceful Shutdown of a Microservice
Task: Implement graceful shutdown handling for an HTTP server.
Prompt:
Write a Go program that starts an HTTP server and handles graceful shutdown on SIGINT/SIGTERM signals. Use signal.Notify and a context with timeout. Show how to drain active connections before exiting.
Example Result:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8080"}
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(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
This pattern is recommended by the Go team for production services.
12. Building a CLI with Bubble Tea (TUI)
Task: Create a terminal user interface (TUI) application using Bubble Tea.
Prompt:
Write a simple interactive to-do list TUI using the Bubble Tea framework (github.com/charmbracelet/bubbletea). The app should display a list of tasks, allow adding new tasks by typing, and mark tasks as done with a key press. Use the tea.Model interface.
Example Result (Model):
type model struct {
tasks []string
cursor int
input string
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q":
return m, tea.Quit
case "enter":
m.tasks = append(m.tasks, m.input)
m.input = ""
}
}
return m, nil
}
func (m model) View() string {
s := "To-Do List\n\n"
for i, task := range m.tasks {
s += fmt.Sprintf("%d. %s\n", i+1, task)
}
s += "\nNew task: " + m.input
return s
}
Bubble Tea is widely used for building modern CLI tools with interactive UIs.
Conclusion
These 12 prompts cover the essential spectrum of Go development: from basic API setup to advanced microservices, CLI tools, and performance optimization. By integrating these prompts into your workflow, you can reduce boilerplate, follow best practices, and build production-ready systems faster.
To get the most out of these prompts, adapt them to your specific context—replace placeholder data, adjust error handling to your standards, and extend the examples with your own business logic. For further reading, consult the official Go documentation at go.dev/doc and the Go blog at go.dev/blog.
Now it's your turn. Pick one prompt from the list, run it with your favorite AI assistant, and build something today. The Go ecosystem is vast, but with the right prompts, you can navigate it efficiently.
Comments