
Golang Backend Development
- 1.2k installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
golang-backend-development provides documented workflows for Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
About
The golang-backend-development skill complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment # Go Backend Development A comprehensive skill for building production-grade backend systems with Go. Master goroutines, channels, web servers, database integration, microservices architecture, and deployment patterns for scalable, concurrent backend applications. ## When to Use This Skill Use this skill when: - Building high-performance web servers and REST APIs - Developing microservices architectures with gRPC or HTTP - Implementing concurrent processing with goroutines and channels - Creating real-time systems requiring high throughput - Building database-backed applications with connection pooling - Developing cloud-native applications for containerized deployment - Writing performance-critical backend services - Building distributed systems with service discovery - Implementing event-driven architectures - Creating CLI tools and system utilities with networking capabilities - Developing WebSocket servers for real-time communication - Building data processing pipelines with concurrent workers.
- Building high-performance web servers and REST APIs
- Developing microservices architectures with gRPC or HTTP
- Implementing concurrent processing with goroutines and channels
- Creating real-time systems requiring high throughput
- Building database-backed applications with connection pooling
Golang Backend Development by the numbers
- 1,156 all-time installs (skills.sh)
- +24 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #83 of 911 Databases skills by installs in the Skillselion catalog
- Security screen: CRITICAL risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
golang-backend-development capabilities & compatibility
- Capabilities
- building high performance web servers and rest a · developing microservices architectures with grpc · implementing concurrent processing with goroutin · creating real time systems requiring high throug · building database backed applications with conne
- Use cases
- documentation
What golang-backend-development says it does
# Go Backend Development A comprehensive skill for building production-grade backend systems with Go.
Master goroutines, channels, web servers, database integration, microservices architecture, and deployment patterns for scalable, concurrent backend applications.
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill golang-backend-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.2k |
|---|---|
| repo stars | ★ 61 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
How do I use golang-backend-development for the task described in its SKILL.md triggers?
Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
Who is it for?
Teams invoking golang-backend-development when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
What you get
Step-by-step guidance grounded in golang-backend-development documentation and reference files.
- REST API handler code
- Middleware and worker pool implementations
- Database CRUD and transaction examples
By the numbers
- 25+ production-ready Go backend examples
- 11 documented pattern categories in the table of contents
Files
Go Backend Development
A comprehensive skill for building production-grade backend systems with Go. Master goroutines, channels, web servers, database integration, microservices architecture, and deployment patterns for scalable, concurrent backend applications.
When to Use This Skill
Use this skill when:
- Building high-performance web servers and REST APIs
- Developing microservices architectures with gRPC or HTTP
- Implementing concurrent processing with goroutines and channels
- Creating real-time systems requiring high throughput
- Building database-backed applications with connection pooling
- Developing cloud-native applications for containerized deployment
- Writing performance-critical backend services
- Building distributed systems with service discovery
- Implementing event-driven architectures
- Creating CLI tools and system utilities with networking capabilities
- Developing WebSocket servers for real-time communication
- Building data processing pipelines with concurrent workers
Go excels at:
- Network programming and HTTP services
- Concurrent processing with lightweight goroutines
- System-level programming with garbage collection
- Cross-platform compilation
- Fast compilation times for rapid development
- Built-in testing and benchmarking
Core Concepts
1. Goroutines: Lightweight Concurrency
Goroutines are lightweight threads managed by the Go runtime. They enable concurrent execution with minimal overhead.
Key Characteristics:
- Extremely lightweight (start with ~2KB stack)
- Multiplexed onto OS threads by the runtime
- Thousands or millions can run concurrently
- Scheduled cooperatively with integrated scheduler
Basic Goroutine Pattern:
func main() {
// Launch concurrent computation
go expensiveComputation(x, y, z)
anotherExpensiveComputation(a, b, c)
}The go keyword launches a new goroutine, allowing expensiveComputation to run concurrently with anotherExpensiveComputation. This is fundamental to Go's concurrency model.
Common Use Cases:
- Background processing
- Concurrent API calls
- Parallel data processing
- Real-time event handling
- Connection handling in servers
2. Channels: Safe Communication
Channels provide type-safe communication between goroutines, eliminating the need for explicit locks in many scenarios.
Channel Types:
// Unbuffered channel - synchronous communication
ch := make(chan int)
// Buffered channel - asynchronous up to buffer size
ch := make(chan int, 100)
// Read-only channel
func receive(ch <-chan int) { /* ... */ }
// Write-only channel
func send(ch chan<- int) { /* ... */ }Synchronization with Channels:
func computeAndSend(ch chan int, x, y, z int) {
ch <- expensiveComputation(x, y, z)
}
func main() {
ch := make(chan int)
go computeAndSend(ch, x, y, z)
v2 := anotherExpensiveComputation(a, b, c)
v1 := <-ch // Block until result available
fmt.Println(v1, v2)
}This pattern ensures both computations complete before proceeding, with the channel providing both communication and synchronization.
Channel Patterns:
- Producer-consumer
- Fan-out/fan-in
- Pipeline stages
- Timeouts and cancellation
- Semaphores and rate limiting
3. Select Statement: Multiplexing Channels
The select statement enables multiplexing multiple channel operations, similar to a switch for channels.
Timeout Implementation:
timeout := make(chan bool, 1)
go func() {
time.Sleep(1 * time.Second)
timeout <- true
}()
select {
case <-ch:
// Read from ch succeeded
case <-timeout:
// Operation timed out
}Context-Based Cancellation:
select {
case result := <-resultCh:
return result
case <-ctx.Done():
return ctx.Err()
}4. Context Package: Request-Scoped Values
The context.Context interface manages deadlines, cancellation signals, and request-scoped values across API boundaries.
Context Interface:
type Context interface {
// Done returns a channel closed when work should be canceled
Done() <-chan struct{}
// Err returns why context was canceled
Err() error
// Deadline returns when work should be canceled
Deadline() (deadline time.Time, ok bool)
// Value returns request-scoped value
Value(key any) any
}Creating Contexts:
// Background context - never canceled
ctx := context.Background()
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// With deadline
deadline := time.Now().Add(10 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
// With values
ctx = context.WithValue(parentCtx, key, value)Best Practices:
- Always pass context as first parameter:
func DoSomething(ctx context.Context, ...) - Call
defer cancel()immediately after creating cancelable context - Propagate context through call chain
- Check
ctx.Done()in long-running operations - Use context values only for request-scoped data, not optional parameters
5. WaitGroup: Coordinating Goroutines
sync.WaitGroup waits for a collection of goroutines to finish.
Basic Pattern:
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Do work
}(i)
}
wg.Wait() // Block until all goroutines completeCommon Use Cases:
- Waiting for parallel tasks
- Coordinating worker pools
- Ensuring cleanup completion
- Synchronizing shutdown
6. Mutex: Protecting Shared State
When shared state is necessary, use sync.Mutex or sync.RWMutex for protection.
Mutex Pattern:
var (
service map[string]net.Addr
serviceMu sync.Mutex
)
func RegisterService(name string, addr net.Addr) {
serviceMu.Lock()
defer serviceMu.Unlock()
service[name] = addr
}
func LookupService(name string) net.Addr {
serviceMu.Lock()
defer serviceMu.Unlock()
return service[name]
}RWMutex for Read-Heavy Workloads:
var (
cache map[string]interface{}
cacheMu sync.RWMutex
)
func Get(key string) interface{} {
cacheMu.RLock()
defer cacheMu.RUnlock()
return cache[key]
}
func Set(key string, value interface{}) {
cacheMu.Lock()
defer cacheMu.Unlock()
cache[key] = value
}7. Concurrent Web Server Pattern
Go's standard pattern for handling concurrent connections:
for {
rw := l.Accept()
conn := newConn(rw, handler)
go conn.serve() // Handle each connection concurrently
}Each accepted connection is handled in its own goroutine, allowing the server to scale to thousands of concurrent connections efficiently.
Web Server Development
HTTP Server Basics
Simple HTTP Server:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe("localhost:8080", nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello!")
}Request Handling Patterns
Handler Functions:
func handler(w http.ResponseWriter, r *http.Request) {
// Read request
method := r.Method
path := r.URL.Path
query := r.URL.Query()
// Write response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"message": "success"}`)
}Handler Structs:
type APIHandler struct {
db *sql.DB
logger *log.Logger
}
func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Access dependencies
h.logger.Printf("Request: %s %s", r.Method, r.URL.Path)
// Handle request
}Middleware Pattern
Logging Middleware:
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))
})
}
// Usage
http.Handle("/api/", loggingMiddleware(apiHandler))Authentication Middleware:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}Chaining Middleware:
handler := loggingMiddleware(authMiddleware(corsMiddleware(apiHandler)))
http.Handle("/api/", handler)Context in HTTP Handlers
HTTP Request with Context:
func handleSearch(w http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Check query parameter
query := req.FormValue("q")
if query == "" {
http.Error(w, "missing query", http.StatusBadRequest)
return
}
// Perform search with context
results, err := performSearch(ctx, query)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Render results
renderTemplate(w, results)
}Context-Aware HTTP Request:
func httpDo(ctx context.Context, req *http.Request,
f func(*http.Response, error) error) error {
c := &http.Client{}
// Run request in goroutine
ch := make(chan error, 1)
go func() {
ch <- f(c.Do(req))
}()
// Wait for completion or cancellation
select {
case <-ctx.Done():
<-ch // Wait for f to return
return ctx.Err()
case err := <-ch:
return err
}
}Routing Patterns
Custom Router:
type Router struct {
routes map[string]http.HandlerFunc
}
func (r *Router) Handle(pattern string, handler http.HandlerFunc) {
r.routes[pattern] = handler
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if handler, ok := r.routes[req.URL.Path]; ok {
handler(w, req)
} else {
http.NotFound(w, req)
}
}RESTful API Structure:
// GET /api/users
func listUsers(w http.ResponseWriter, r *http.Request) { /* ... */ }
// GET /api/users/:id
func getUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// POST /api/users
func createUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// PUT /api/users/:id
func updateUser(w http.ResponseWriter, r *http.Request) { /* ... */ }
// DELETE /api/users/:id
func deleteUser(w http.ResponseWriter, r *http.Request) { /* ... */ }Concurrency Patterns
1. Pipeline Pattern
Pipelines process data through multiple stages connected by channels.
Generator Stage:
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}Processing Stage:
func sq(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}Pipeline Usage:
func main() {
// Set up pipeline
c := gen(2, 3)
out := sq(c)
// Consume output
for n := range out {
fmt.Println(n) // 4 then 9
}
}Buffered Generator (No Goroutine Needed):
func gen(nums ...int) <-chan int {
out := make(chan int, len(nums))
for _, n := range nums {
out <- n
}
close(out)
return out
}2. Fan-Out/Fan-In Pattern
Distribute work across multiple workers and merge results.
Fan-Out: Multiple Workers:
func main() {
in := gen(2, 3, 4, 5)
// Fan out: distribute work across two goroutines
c1 := sq(in)
c2 := sq(in)
// Fan in: merge results
for n := range merge(c1, c2) {
fmt.Println(n)
}
}Merge Function (Fan-In):
func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
// Start output goroutine for each input channel
output := func(c <-chan int) {
for n := range c {
out <- n
}
wg.Done()
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// Close out once all outputs are done
go func() {
wg.Wait()
close(out)
}()
return out
}3. Explicit Cancellation Pattern
Cancellation with Done Channel:
func sq(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-done:
return
}
}
}()
return out
}Broadcasting Cancellation:
func main() {
done := make(chan struct{})
defer close(done) // Broadcast to all goroutines
in := gen(done, 2, 3, 4)
c1 := sq(done, in)
c2 := sq(done, in)
// Process subset of results
out := merge(done, c1, c2)
fmt.Println(<-out)
// done closed on return, canceling all pipeline stages
}Merge with Cancellation:
func merge(done <-chan struct{}, cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
defer wg.Done()
for n := range c {
select {
case out <- n:
case <-done:
return
}
}
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}4. Worker Pool Pattern
Fixed Number of Workers:
func handle(queue chan *Request) {
for r := range queue {
process(r)
}
}
func Serve(clientRequests chan *Request, quit chan bool) {
// Start handlers
for i := 0; i < MaxOutstanding; i++ {
go handle(clientRequests)
}
<-quit // Wait to exit
}Semaphore Pattern:
var sem = make(chan int, MaxOutstanding)
func handle(r *Request) {
sem <- 1 // Acquire
process(r)
<-sem // Release
}
func Serve(queue chan *Request) {
for req := range queue {
go handle(req)
}
}Limiting Goroutine Creation:
func Serve(queue chan *Request) {
for req := range queue {
sem <- 1 // Acquire before creating goroutine
go func() {
process(req)
<-sem // Release
}()
}
}5. Query Racing Pattern
Query multiple sources and return first result:
func Query(conns []Conn, query string) Result {
ch := make(chan Result)
for _, conn := range conns {
go func(c Conn) {
select {
case ch <- c.DoQuery(query):
default:
}
}(conn)
}
return <-ch
}6. Parallel Processing Example
Serial MD5 Calculation:
func MD5All(root string) (map[string][md5.Size]byte, error) {
m := make(map[string][md5.Size]byte)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
m[path] = md5.Sum(data)
return nil
})
return m, err
}Parallel MD5 with Pipeline:
type result struct {
path string
sum [md5.Size]byte
err error
}
func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) {
c := make(chan result)
errc := make(chan error, 1)
go func() {
defer close(c)
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
// Start goroutine for each file
go func() {
data, err := ioutil.ReadFile(path)
select {
case c <- result{path, md5.Sum(data), err}:
case <-done:
}
}()
// Check for early cancellation
select {
case <-done:
return errors.New("walk canceled")
default:
return nil
}
})
select {
case errc <- err:
case <-done:
}
}()
return c, errc
}
func MD5All(root string) (map[string][md5.Size]byte, error) {
done := make(chan struct{})
defer close(done)
c, errc := sumFiles(done, root)
m := make(map[string][md5.Size]byte)
for r := range c {
if r.err != nil {
return nil, r.err
}
m[r.path] = r.sum
}
if err := <-errc; err != nil {
return nil, err
}
return m, nil
}7. Leaky Buffer Pattern
Efficient buffer reuse:
var freeList = make(chan *Buffer, 100)
func server() {
for {
b := <-serverChan // Wait for work
process(b)
// Try to reuse buffer
select {
case freeList <- b:
// Buffer on free list
default:
// Free list full, GC will reclaim
}
}
}Database Integration
Connection Management
Database Connection Pool:
import "database/sql"
func initDB(dataSourceName string) (*sql.DB, error) {
db, err := sql.Open("postgres", dataSourceName)
if err != nil {
return nil, err
}
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(10 * time.Minute)
// Verify connection
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}Query Patterns
Single Row Query:
func getUser(db *sql.DB, userID int) (*User, error) {
user := &User{}
err := db.QueryRow(
"SELECT id, name, email FROM users WHERE id = $1",
userID,
).Scan(&user.ID, &user.Name, &user.Email)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user not found")
}
if err != nil {
return nil, err
}
return user, nil
}Multiple Row Query:
func listUsers(db *sql.DB) ([]*User, error) {
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {
return nil, err
}
users = append(users, user)
}
if err := rows.Err(); err != nil {
return nil, err
}
return users, nil
}Insert/Update with Context:
func createUser(ctx context.Context, db *sql.DB, user *User) error {
query := "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"
err := db.QueryRowContext(ctx, query, user.Name, user.Email).Scan(&user.ID)
return err
}Transaction Handling
func transferFunds(ctx context.Context, db *sql.DB, from, to int, amount decimal.Decimal) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback() // Rollback if not committed
// Debit from account
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
amount, from)
if err != nil {
return err
}
// Credit to account
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
amount, to)
if err != nil {
return err
}
return tx.Commit()
}Prepared Statements
func insertUsers(db *sql.DB, users []*User) error {
stmt, err := db.Prepare("INSERT INTO users (name, email) VALUES ($1, $2)")
if err != nil {
return err
}
defer stmt.Close()
for _, user := range users {
_, err := stmt.Exec(user.Name, user.Email)
if err != nil {
return err
}
}
return nil
}Error Handling
Custom Error Types
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Message)
}
// Usage
if email == "" {
return &ValidationError{Field: "email", Message: "required"}
}Error Wrapping
import "fmt"
func processData(data []byte) error {
err := validateData(data)
if err != nil {
return fmt.Errorf("process data: %w", err)
}
return nil
}
// Unwrapping
if errors.Is(err, ErrValidation) {
// Handle validation error
}
if errors.As(err, &validationErr) {
// Access ValidationError fields
}Sentinel Errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
// Usage
if errors.Is(err, ErrNotFound) {
http.Error(w, "Resource not found", http.StatusNotFound)
}Testing
Unit Tests
func TestGetUser(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
user, err := getUser(db, 1)
if err != nil {
t.Fatalf("getUser failed: %v", err)
}
if user.Name != "John Doe" {
t.Errorf("expected name John Doe, got %s", user.Name)
}
}Table-Driven Tests
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{"valid email", "user@example.com", false},
{"missing @", "userexample.com", true},
{"empty string", "", true},
{"missing domain", "user@", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("validateEmail(%q) error = %v, wantErr %v",
tt.email, err, tt.wantErr)
}
})
}
}Benchmarks
func BenchmarkConcurrentMap(b *testing.B) {
m := make(map[string]int)
var mu sync.Mutex
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
m["key"]++
mu.Unlock()
}
})
}HTTP Handler Testing
func TestHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/api/users", nil)
w := httptest.NewRecorder()
handler(w, req)
resp := w.Result()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status 200, got %d", resp.StatusCode)
}
body, _ := ioutil.ReadAll(resp.Body)
// Assert body content
}Production Patterns
Graceful Shutdown
func main() {
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
// Start server in goroutine
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal
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 := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}Configuration Management
type Config struct {
ServerPort int `env:"PORT" envDefault:"8080"`
DBHost string `env:"DB_HOST" envDefault:"localhost"`
DBPort int `env:"DB_PORT" envDefault:"5432"`
LogLevel string `env:"LOG_LEVEL" envDefault:"info"`
Timeout time.Duration `env:"TIMEOUT" envDefault:"30s"`
}
func loadConfig() (*Config, error) {
cfg := &Config{}
if err := env.Parse(cfg); err != nil {
return nil, err
}
return cfg, nil
}Structured Logging
import "log/slog"
func setupLogger() *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
}
func handler(w http.ResponseWriter, r *http.Request) {
logger := slog.With(
"method", r.Method,
"path", r.URL.Path,
"remote", r.RemoteAddr,
)
logger.Info("handling request")
// Process request
logger.Info("request completed", "status", 200)
}Health Checks
func healthHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Check database
if err := db.Ping(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"status": "unhealthy",
"error": err.Error(),
})
return
}
// Check other dependencies...
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "healthy",
})
}
}Rate Limiting
import "golang.org/x/time/rate"
func rateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// Usage
limiter := rate.NewLimiter(rate.Limit(10), 20) // 10 req/sec, burst 20
handler := rateLimitMiddleware(limiter)(apiHandler)Panic Recovery
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}Microservices Patterns
Service Structure
type UserService struct {
db *sql.DB
cache *redis.Client
logger *slog.Logger
}
func NewUserService(db *sql.DB, cache *redis.Client, logger *slog.Logger) *UserService {
return &UserService{
db: db,
cache: cache,
logger: logger,
}
}
func (s *UserService) GetUser(ctx context.Context, userID string) (*User, error) {
// Check cache first
if user, err := s.getFromCache(ctx, userID); err == nil {
return user, nil
}
// Query database
user, err := s.getFromDB(ctx, userID)
if err != nil {
return nil, err
}
// Update cache
go s.updateCache(context.Background(), user)
return user, nil
}gRPC Service
type server struct {
pb.UnimplementedUserServiceServer
db *sql.DB
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user := &pb.User{}
err := s.db.QueryRowContext(ctx,
"SELECT id, name, email FROM users WHERE id = $1",
req.GetId(),
).Scan(&user.Id, &user.Name, &user.Email)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found")
}
return user, nil
}Service Discovery
type ServiceRegistry struct {
services map[string][]string
mu sync.RWMutex
}
func (r *ServiceRegistry) Register(name, addr string) {
r.mu.Lock()
defer r.mu.Unlock()
r.services[name] = append(r.services[name], addr)
}
func (r *ServiceRegistry) Discover(name string) (string, error) {
r.mu.RLock()
defer r.mu.RUnlock()
addrs := r.services[name]
if len(addrs) == 0 {
return "", fmt.Errorf("service %s not found", name)
}
// Simple round-robin
return addrs[rand.Intn(len(addrs))], nil
}Circuit Breaker
type CircuitBreaker struct {
maxFailures int
timeout time.Duration
failures int
lastFailure time.Time
state string // closed, open, half-open
mu sync.Mutex
}
func (cb *CircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
if cb.state == "open" {
if time.Since(cb.lastFailure) > cb.timeout {
cb.state = "half-open"
} else {
cb.mu.Unlock()
return errors.New("circuit breaker open")
}
}
cb.mu.Unlock()
err := fn()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures++
cb.lastFailure = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = "open"
}
return err
}
cb.failures = 0
cb.state = "closed"
return nil
}Best Practices
1. Goroutine Management
- Always consider goroutine lifecycle and cleanup
- Use contexts for cancellation propagation
- Avoid goroutine leaks by ensuring all goroutines can exit
- Be cautious with closures in loops - pass values explicitly
Anti-pattern:
for _, v := range values {
go func() {
fmt.Println(v) // All goroutines share same v
}()
}Correct:
for _, v := range values {
go func(val string) {
fmt.Println(val) // Each goroutine gets its own copy
}(v)
}2. Channel Best Practices
- Close channels from sender, not receiver
- Use buffered channels to prevent goroutine leaks
- Consider using
selectwithdefaultfor non-blocking operations - Remember: sending on closed channel panics, receiving returns zero value
3. Error Handling
- Return errors, don't panic (except for truly exceptional cases)
- Wrap errors with context using
fmt.Errorf("%w", err) - Use custom error types for programmatic handling
- Log errors with sufficient context
4. Performance
- Use
sync.Poolfor frequently allocated objects - Profile before optimizing:
go test -bench . -cpuprofile=cpu.prof - Consider
sync.Mapfor concurrent map access patterns - Use buffered channels for known capacity
- Avoid unnecessary allocations in hot paths
5. Code Organization
project/
├── cmd/
│ └── server/
│ └── main.go # Application entry point
├── internal/
│ ├── api/ # HTTP handlers
│ ├── service/ # Business logic
│ ├── repository/ # Data access
│ └── middleware/ # HTTP middleware
├── pkg/
│ └── utils/ # Public utilities
├── migrations/ # Database migrations
├── config/ # Configuration files
└── docker/ # Docker files6. Security
- Validate all inputs
- Use prepared statements for SQL queries
- Implement rate limiting
- Use HTTPS in production
- Sanitize error messages sent to clients
- Use context timeouts to prevent resource exhaustion
- Implement proper authentication and authorization
7. Testing
- Write table-driven tests
- Use
t.Helper()for test helper functions - Mock external dependencies
- Use
httptestfor HTTP handler testing - Write benchmarks for performance-critical code
- Aim for >80% test coverage on business logic
Common Pitfalls
1. Race Conditions
Problem:
var service map[string]net.Addr
func RegisterService(name string, addr net.Addr) {
service[name] = addr // RACE CONDITION
}
func LookupService(name string) net.Addr {
return service[name] // RACE CONDITION
}Solution:
var (
service map[string]net.Addr
serviceMu sync.Mutex
)
func RegisterService(name string, addr net.Addr) {
serviceMu.Lock()
defer serviceMu.Unlock()
service[name] = addr
}2. Goroutine Leaks
Problem:
func process() {
ch := make(chan int)
go func() {
ch <- expensive() // Blocks forever if no receiver
}()
// Returns without reading from ch
}Solution:
func process() {
ch := make(chan int, 1) // Buffered channel
go func() {
ch <- expensive() // Won't block
}()
}3. Not Closing Channels
Receivers need to know when no more values are coming:
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out) // IMPORTANT: close when done
}()
return out
}4. Blocking on Unbuffered Channels
// This will deadlock
ch := make(chan int)
ch <- 1 // Blocks forever - no receiver
v := <-chUse buffered channels or separate goroutines.
5. Unsynchronized Channel Operations
c := make(chan struct{})
// RACE CONDITION
go func() { c <- struct{}{} }()
close(c)Ensure happens-before relationship with proper synchronization.
Resources and References
Official Documentation
- Go Documentation: https://go.dev/doc/
- Effective Go: https://go.dev/doc/effective_go
- Go Blog: https://go.dev/blog/
- Go by Example: https://gobyexample.com/
Concurrency Resources
- Go Concurrency Patterns: https://go.dev/blog/pipelines
- Context Package: https://go.dev/blog/context
- Share Memory By Communicating: https://go.dev/blog/codelab-share
Standard Library
- net/http: https://pkg.go.dev/net/http
- database/sql: https://pkg.go.dev/database/sql
- context: https://pkg.go.dev/context
- sync: https://pkg.go.dev/sync
Tools
- Race Detector:
go test -race - Profiler:
go tool pprof - Benchmarking:
go test -bench - Static Analysis:
go vet,staticcheck
---
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Backend Development, Systems Programming, Concurrent Programming Prerequisites: Basic programming knowledge, understanding of HTTP, familiarity with command line Recommended Next Skills: docker-deployment, kubernetes-orchestration, grpc-microservices
Go Backend Development Examples
25+ practical, production-ready examples demonstrating Go backend patterns, from basic HTTP servers to advanced microservices.
Table of Contents
1. Basic Web Server 2. JSON REST API 3. Middleware Chain 4. Context-Based Timeout 5. Worker Pool Pattern 6. Pipeline Pattern 7. Fan-Out/Fan-In Pattern 8. Database CRUD Operations 9. Transaction Handling 10. Graceful Shutdown 11. Rate Limiting 12. Circuit Breaker 13. WebSocket Server 14. gRPC Service 15. Caching Layer 16. Event-Driven Architecture 17. File Upload Handler 18. Authentication Middleware 19. Structured Logging 20. Health Check System 21. Concurrent File Processing 22. Service Discovery 23. Message Queue Consumer 24. Background Job Processor 25. API Gateway Pattern
---
1. Basic Web Server
A minimal HTTP server demonstrating Go's built-in web capabilities.
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// Register handler function
http.HandleFunc("/", handleRoot)
http.HandleFunc("/health", handleHealth)
// Start server
log.Println("Server starting on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
func handleRoot(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
}Use Case: Simple web services, health check endpoints, landing pages
Key Concepts:
http.HandleFuncregisters handlershttp.ListenAndServestarts the server- Handler functions receive
ResponseWriterand*Request
---
2. JSON REST API
Complete RESTful API with JSON encoding/decoding.
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type UserStore struct {
users map[int]*User
mu sync.RWMutex
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]*User),
nextID: 1,
}
}
func (s *UserStore) Create(user *User) *User {
s.mu.Lock()
defer s.mu.Unlock()
user.ID = s.nextID
s.nextID++
s.users[user.ID] = user
return user
}
func (s *UserStore) Get(id int) (*User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
user, ok := s.users[id]
return user, ok
}
func (s *UserStore) List() []*User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]*User, 0, len(s.users))
for _, user := range s.users {
users = append(users, user)
}
return users
}
func main() {
store := NewUserStore()
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
handleListUsers(w, r, store)
case http.MethodPost:
handleCreateUser(w, r, store)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
http.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
handleGetUser(w, r, store)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleListUsers(w http.ResponseWriter, r *http.Request, store *UserStore) {
users := store.List()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func handleCreateUser(w http.ResponseWriter, r *http.Request, store *UserStore) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
created := store.Create(&user)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(created)
}
func handleGetUser(w http.ResponseWriter, r *http.Request, store *UserStore) {
// Extract ID from path (simple parsing)
idStr := r.URL.Path[len("/users/"):]
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user, ok := store.Get(id)
if !ok {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}Use Case: RESTful APIs, CRUD operations, microservice endpoints
Key Concepts:
- JSON encoding/decoding with struct tags
- Thread-safe data access with
sync.RWMutex - HTTP method routing
- Status code handling
---
3. Middleware Chain
Composable middleware for cross-cutting concerns.
package main
import (
"log"
"net/http"
"time"
)
type Middleware func(http.Handler) http.Handler
// Chain multiple middleware
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
// Logging middleware
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))
})
}
// CORS middleware
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)
})
}
// Recovery middleware
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic recovered: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// Request ID middleware
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = generateRequestID()
}
w.Header().Set("X-Request-ID", requestID)
next.ServeHTTP(w, r)
})
}
func generateRequestID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
func main() {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
})
// Chain middleware
wrapped := Chain(handler,
RecoveryMiddleware,
LoggingMiddleware,
CORSMiddleware,
RequestIDMiddleware,
)
http.Handle("/", wrapped)
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: Adding logging, CORS, authentication, recovery to handlers
Key Concepts:
- Middleware as higher-order functions
- Composable handler chain
- Request/response wrapping
---
4. Context-Based Timeout
Using context for request timeouts and cancellation.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/search", handleSearch)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleSearch(w http.ResponseWriter, r *http.Request) {
// Create context with timeout
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
query := r.URL.Query().Get("q")
if query == "" {
http.Error(w, "missing query parameter", http.StatusBadRequest)
return
}
// Perform search with context
results, err := performSearch(ctx, query)
if err != nil {
if err == context.DeadlineExceeded {
http.Error(w, "Search timeout", http.StatusRequestTimeout)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}
type SearchResult struct {
Query string `json:"query"`
Results []string `json:"results"`
Took string `json:"took"`
}
func performSearch(ctx context.Context, query string) (*SearchResult, error) {
start := time.Now()
// Simulate search with channels
resultCh := make(chan []string, 1)
errCh := make(chan error, 1)
go func() {
// Simulate expensive search operation
time.Sleep(2 * time.Second)
// Simulate results
results := []string{
fmt.Sprintf("Result 1 for %s", query),
fmt.Sprintf("Result 2 for %s", query),
fmt.Sprintf("Result 3 for %s", query),
}
resultCh <- results
}()
// Wait for result or timeout
select {
case results := <-resultCh:
return &SearchResult{
Query: query,
Results: results,
Took: time.Since(start).String(),
}, nil
case err := <-errCh:
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
// HTTP client with context
func httpDo(ctx context.Context, req *http.Request,
f func(*http.Response, error) error) error {
client := &http.Client{}
ch := make(chan error, 1)
go func() {
ch <- f(client.Do(req))
}()
select {
case <-ctx.Done():
// Wait for f to return
<-ch
return ctx.Err()
case err := <-ch:
return err
}
}Use Case: Preventing long-running requests, enforcing SLAs, cancellation
Key Concepts:
- Context for cancellation and timeouts
- Select statement for multiplexing
- Goroutine coordination with channels
---
5. Worker Pool Pattern
Fixed number of workers processing jobs from a queue.
package main
import (
"fmt"
"log"
"sync"
"time"
)
type Job struct {
ID int
Data string
}
type Result struct {
Job Job
Output string
Err error
}
func worker(id int, jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
log.Printf("Worker %d processing job %d", id, job.ID)
// Simulate work
time.Sleep(time.Second)
results <- Result{
Job: job,
Output: fmt.Sprintf("Processed: %s", job.Data),
Err: nil,
}
}
log.Printf("Worker %d finished", id)
}
func main() {
const numWorkers = 3
const numJobs = 10
jobs := make(chan Job, numJobs)
results := make(chan Result, numJobs)
var wg sync.WaitGroup
// Start workers
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
go func() {
for j := 1; j <= numJobs; j++ {
jobs <- Job{
ID: j,
Data: fmt.Sprintf("Job data %d", j),
}
}
close(jobs)
}()
// Wait for workers to complete
go func() {
wg.Wait()
close(results)
}()
// Collect results
for result := range results {
if result.Err != nil {
log.Printf("Job %d failed: %v", result.Job.ID, result.Err)
} else {
log.Printf("Job %d completed: %s", result.Job.ID, result.Output)
}
}
log.Println("All jobs completed")
}Use Case: Batch processing, concurrent task execution, resource pooling
Key Concepts:
- Fixed worker pool
- Job distribution via channels
- Result collection
- WaitGroup for synchronization
---
6. Pipeline Pattern
Multi-stage data processing with channels.
package main
import (
"fmt"
)
// Stage 1: Generate numbers
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
// Stage 2: Square numbers
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
// Stage 3: Filter even numbers
func filterEven(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
if n%2 == 0 {
out <- n
}
}
}()
return out
}
// Stage 4: Sum numbers
func sum(in <-chan int) int {
total := 0
for n := range in {
total += n
}
return total
}
func main() {
// Build pipeline
numbers := generate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
squared := square(numbers)
evens := filterEven(squared)
result := sum(evens)
fmt.Printf("Result: %d\n", result)
// Output: 220 (4 + 16 + 36 + 64 + 100)
}
// Pipeline with cancellation
func generateWithDone(done <-chan struct{}, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-done:
return
}
}
}()
return out
}
func squareWithDone(done <-chan struct{}, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-done:
return
}
}
}()
return out
}Use Case: Data transformation, ETL pipelines, stream processing
Key Concepts:
- Pipeline stages as functions returning channels
- Closing channels to signal completion
- Optional cancellation with done channel
---
7. Fan-Out/Fan-In Pattern
Distribute work across multiple workers and merge results.
package main
import (
"fmt"
"sync"
)
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
out <- n * n
}
}()
return out
}
// Merge multiple channels into one (fan-in)
func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
// Start output goroutine for each input channel
output := func(c <-chan int) {
defer wg.Done()
for n := range c {
out <- n
}
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
// Close out once all outputs are done
go func() {
wg.Wait()
close(out)
}()
return out
}
func main() {
in := generate(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// Fan out: distribute work across multiple workers
c1 := square(in)
c2 := square(in)
c3 := square(in)
// Fan in: merge results
for n := range merge(c1, c2, c3) {
fmt.Println(n)
}
}
// Advanced merge with cancellation
func mergeWithDone(done <-chan struct{}, cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
defer wg.Done()
for n := range c {
select {
case out <- n:
case <-done:
return
}
}
}
wg.Add(len(cs))
for _, c := range cs {
go output(c)
}
go func() {
wg.Wait()
close(out)
}()
return out
}Use Case: Parallel processing, load distribution, aggregating results
Key Concepts:
- Fan-out: multiple workers reading from same channel
- Fan-in: merging multiple channels into one
- WaitGroup for coordination
---
8. Database CRUD Operations
Complete CRUD operations with PostgreSQL.
package main
import (
"context"
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
type User struct {
ID int
Name string
Email string
CreatedAt time.Time
UpdatedAt time.Time
}
type UserRepository struct {
db *sql.DB
}
func NewUserRepository(db *sql.DB) *UserRepository {
return &UserRepository{db: db}
}
// Create user
func (r *UserRepository) Create(ctx context.Context, user *User) error {
query := `
INSERT INTO users (name, email, created_at, updated_at)
VALUES ($1, $2, NOW(), NOW())
RETURNING id, created_at, updated_at
`
err := r.db.QueryRowContext(ctx, query, user.Name, user.Email).Scan(
&user.ID,
&user.CreatedAt,
&user.UpdatedAt,
)
return err
}
// Get user by ID
func (r *UserRepository) GetByID(ctx context.Context, id int) (*User, error) {
user := &User{}
query := `
SELECT id, name, email, created_at, updated_at
FROM users
WHERE id = $1
`
err := r.db.QueryRowContext(ctx, query, id).Scan(
&user.ID,
&user.Name,
&user.Email,
&user.CreatedAt,
&user.UpdatedAt,
)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user not found")
}
return user, err
}
// List all users
func (r *UserRepository) List(ctx context.Context) ([]*User, error) {
query := `
SELECT id, name, email, created_at, updated_at
FROM users
ORDER BY created_at DESC
`
rows, err := r.db.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
user := &User{}
if err := rows.Scan(&user.ID, &user.Name, &user.Email,
&user.CreatedAt, &user.UpdatedAt); err != nil {
return nil, err
}
users = append(users, user)
}
return users, rows.Err()
}
// Update user
func (r *UserRepository) Update(ctx context.Context, user *User) error {
query := `
UPDATE users
SET name = $1, email = $2, updated_at = NOW()
WHERE id = $3
RETURNING updated_at
`
err := r.db.QueryRowContext(ctx, query, user.Name, user.Email, user.ID).Scan(
&user.UpdatedAt,
)
return err
}
// Delete user
func (r *UserRepository) Delete(ctx context.Context, id int) error {
query := `DELETE FROM users WHERE id = $1`
result, err := r.db.ExecContext(ctx, query, id)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err != nil {
return err
}
if rows == 0 {
return fmt.Errorf("user not found")
}
return nil
}
func main() {
// Connect to database
connStr := "host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Configure connection pool
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
// Ping database
if err := db.Ping(); err != nil {
log.Fatal(err)
}
repo := NewUserRepository(db)
ctx := context.Background()
// Create user
user := &User{
Name: "John Doe",
Email: "john@example.com",
}
if err := repo.Create(ctx, user); err != nil {
log.Fatal(err)
}
fmt.Printf("Created user: %+v\n", user)
// Get user
found, err := repo.GetByID(ctx, user.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found user: %+v\n", found)
// List users
users, err := repo.List(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total users: %d\n", len(users))
// Update user
user.Name = "Jane Doe"
if err := repo.Update(ctx, user); err != nil {
log.Fatal(err)
}
fmt.Printf("Updated user: %+v\n", user)
// Delete user
if err := repo.Delete(ctx, user.ID); err != nil {
log.Fatal(err)
}
fmt.Println("User deleted")
}Use Case: Database-backed applications, data persistence, repository pattern
Key Concepts:
- Connection pooling
- Context for cancellation
- Prepared statements
- Error handling for no rows
---
9. Transaction Handling
Managing database transactions with rollback.
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
type TransferService struct {
db *sql.DB
}
func NewTransferService(db *sql.DB) *TransferService {
return &TransferService{db: db}
}
// Transfer funds between accounts
func (s *TransferService) Transfer(ctx context.Context, fromID, toID int, amount float64) error {
// Start transaction
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin transaction: %w", err)
}
// Defer rollback - will be no-op if commit succeeds
defer tx.Rollback()
// Debit from account
var balance float64
err = tx.QueryRowContext(ctx,
"SELECT balance FROM accounts WHERE id = $1 FOR UPDATE",
fromID).Scan(&balance)
if err != nil {
return fmt.Errorf("get from account: %w", err)
}
if balance < amount {
return fmt.Errorf("insufficient funds")
}
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance - $1 WHERE id = $2",
amount, fromID)
if err != nil {
return fmt.Errorf("debit account: %w", err)
}
// Credit to account
_, err = tx.ExecContext(ctx,
"UPDATE accounts SET balance = balance + $1 WHERE id = $2",
amount, toID)
if err != nil {
return fmt.Errorf("credit account: %w", err)
}
// Record transaction
_, err = tx.ExecContext(ctx,
`INSERT INTO transactions (from_account, to_account, amount, created_at)
VALUES ($1, $2, $3, NOW())`,
fromID, toID, amount)
if err != nil {
return fmt.Errorf("record transaction: %w", err)
}
// Commit transaction
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit transaction: %w", err)
}
return nil
}
// Batch insert with transaction
func (s *TransferService) BatchInsert(ctx context.Context, users []User) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
stmt, err := tx.PrepareContext(ctx,
"INSERT INTO users (name, email) VALUES ($1, $2)")
if err != nil {
return err
}
defer stmt.Close()
for _, user := range users {
if _, err := stmt.ExecContext(ctx, user.Name, user.Email); err != nil {
return err // Triggers rollback
}
}
return tx.Commit()
}
func main() {
db, err := sql.Open("postgres", "...")
if err != nil {
log.Fatal(err)
}
defer db.Close()
service := NewTransferService(db)
ctx := context.Background()
// Transfer $100 from account 1 to account 2
err = service.Transfer(ctx, 1, 2, 100.00)
if err != nil {
log.Printf("Transfer failed: %v", err)
} else {
log.Println("Transfer successful")
}
}Use Case: Financial transactions, atomic operations, data consistency
Key Concepts:
- Transaction lifecycle (Begin, Commit, Rollback)
- Deferred rollback for safety
- Row locking with
FOR UPDATE - Error wrapping for context
---
10. Graceful Shutdown
Properly shutting down HTTP server and cleaning up resources.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// Create server
srv := &http.Server{
Addr: ":8080",
Handler: setupRoutes(),
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server in goroutine
go func() {
log.Println("Server starting on :8080")
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed to start: %v", err)
}
}()
// Wait for interrupt signal
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Server is shutting down...")
// Create shutdown context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Attempt graceful shutdown
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited gracefully")
}
func setupRoutes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Simulate long request
time.Sleep(2 * time.Second)
w.Write([]byte("Hello, World!"))
})
return mux
}
// Complete shutdown example with cleanup
type Application struct {
server *http.Server
db *sql.DB
logger *log.Logger
}
func (app *Application) Shutdown(ctx context.Context) error {
app.logger.Println("Starting shutdown sequence...")
// Shutdown HTTP server
if err := app.server.Shutdown(ctx); err != nil {
return fmt.Errorf("http server shutdown: %w", err)
}
app.logger.Println("HTTP server stopped")
// Close database connections
if err := app.db.Close(); err != nil {
return fmt.Errorf("database close: %w", err)
}
app.logger.Println("Database connections closed")
// Perform other cleanup tasks
// - Flush metrics
// - Close message queues
// - Save state
app.logger.Println("Shutdown complete")
return nil
}Use Case: Production deployments, zero-downtime deploys, resource cleanup
Key Concepts:
- Signal handling for interrupts
- Graceful HTTP server shutdown
- Timeout for shutdown operations
- Resource cleanup sequence
---
11. Rate Limiting
Implementing rate limiting middleware.
package main
import (
"net/http"
"sync"
"time"
"golang.org/x/time/rate"
)
// Simple rate limiter using golang.org/x/time/rate
func rateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// Per-IP rate limiter
type IPRateLimiter struct {
limiters map[string]*rate.Limiter
mu sync.RWMutex
rate rate.Limit
burst int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
limiters: make(map[string]*rate.Limiter),
rate: r,
burst: b,
}
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter, exists := i.limiters[ip]
if !exists {
limiter = rate.NewLimiter(i.rate, i.burst)
i.limiters[ip] = limiter
}
return limiter
}
func (i *IPRateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
limiter := i.GetLimiter(ip)
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
// Cleanup old limiters
func (i *IPRateLimiter) CleanupOldLimiters() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
i.mu.Lock()
for ip, limiter := range i.limiters {
if limiter.Tokens() == float64(i.burst) {
delete(i.limiters, ip)
}
}
i.mu.Unlock()
}
}
func main() {
// Global rate limiter: 10 req/sec, burst 20
globalLimiter := rate.NewLimiter(10, 20)
// Per-IP rate limiter: 5 req/sec per IP, burst 10
ipLimiter := NewIPRateLimiter(5, 10)
go ipLimiter.CleanupOldLimiters()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Success!"))
})
// Apply rate limiting
http.Handle("/api/",
rateLimitMiddleware(globalLimiter)(
ipLimiter.Middleware(handler)))
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: API protection, preventing abuse, enforcing quotas
Key Concepts:
- Token bucket algorithm
- Per-IP rate limiting
- Limiter cleanup
- Middleware pattern
---
12. Circuit Breaker
Preventing cascading failures with circuit breaker pattern.
package main
import (
"errors"
"fmt"
"sync"
"time"
)
type State int
const (
StateClosed State = iota
StateOpen
StateHalfOpen
)
type CircuitBreaker struct {
maxFailures int
timeout time.Duration
failures int
lastFailTime time.Time
state State
mu sync.Mutex
}
func NewCircuitBreaker(maxFailures int, timeout time.Duration) *CircuitBreaker {
return &CircuitBreaker{
maxFailures: maxFailures,
timeout: timeout,
state: StateClosed,
}
}
var ErrCircuitOpen = errors.New("circuit breaker is open")
func (cb *CircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
// Check if we should transition to half-open
if cb.state == StateOpen {
if time.Since(cb.lastFailTime) > cb.timeout {
cb.state = StateHalfOpen
fmt.Println("Circuit breaker: Transitioning to half-open")
} else {
cb.mu.Unlock()
return ErrCircuitOpen
}
}
cb.mu.Unlock()
// Execute function
err := fn()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures++
cb.lastFailTime = time.Now()
if cb.failures >= cb.maxFailures {
cb.state = StateOpen
fmt.Println("Circuit breaker: Opening circuit")
}
return err
}
// Success - reset failures
if cb.state == StateHalfOpen {
cb.state = StateClosed
fmt.Println("Circuit breaker: Closing circuit")
}
cb.failures = 0
return nil
}
func (cb *CircuitBreaker) State() State {
cb.mu.Lock()
defer cb.mu.Unlock()
return cb.state
}
// Example usage
func main() {
cb := NewCircuitBreaker(3, 5*time.Second)
// Simulated unreliable service
callCount := 0
unreliableService := func() error {
callCount++
if callCount <= 5 {
return errors.New("service unavailable")
}
return nil
}
// Make calls
for i := 0; i < 10; i++ {
err := cb.Call(unreliableService)
if err != nil {
fmt.Printf("Call %d failed: %v (state: %v)\n", i+1, err, cb.State())
} else {
fmt.Printf("Call %d succeeded (state: %v)\n", i+1, cb.State())
}
time.Sleep(1 * time.Second)
}
}Use Case: Microservices resilience, preventing cascading failures
Key Concepts:
- Three states: Closed, Open, Half-Open
- Failure counting
- Timeout-based recovery
- State transitions
---
13. WebSocket Server
Real-time bidirectional communication with WebSockets.
package main
import (
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Allow all origins in development
},
}
type Client struct {
conn *websocket.Conn
send chan []byte
}
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
mu sync.RWMutex
}
func NewHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.mu.Lock()
h.clients[client] = true
h.mu.Unlock()
log.Printf("Client connected. Total: %d", len(h.clients))
case client := <-h.unregister:
h.mu.Lock()
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
h.mu.Unlock()
log.Printf("Client disconnected. Total: %d", len(h.clients))
case message := <-h.broadcast:
h.mu.RLock()
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
h.mu.RUnlock()
}
}
}
func (c *Client) readPump(hub *Hub) {
defer func() {
hub.unregister <- c
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err,
websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
log.Printf("Received: %s", message)
hub.broadcast <- message
}
}
func (c *Client) writePump() {
defer c.conn.Close()
for message := range c.send {
if err := c.conn.WriteMessage(websocket.TextMessage, message); err != nil {
return
}
}
}
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := &Client{
conn: conn,
send: make(chan []byte, 256),
}
hub.register <- client
go client.writePump()
go client.readPump(hub)
}
func main() {
hub := NewHub()
go hub.Run()
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(hub, w, r)
})
log.Println("WebSocket server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: Chat applications, real-time dashboards, live notifications
Key Concepts:
- WebSocket upgrade
- Hub pattern for broadcasting
- Read/write pumps
- Client management
---
14. gRPC Service
Building gRPC services for efficient microservice communication.
// user.proto
syntax = "proto3";
package user;
option go_package = "github.com/example/user/pb";
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc CreateUser(CreateUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
}
message GetUserRequest {
int32 id = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message ListUsersRequest {
int32 limit = 1;
int32 offset = 2;
}
message ListUsersResponse {
repeated User users = 1;
}// server.go
package main
import (
"context"
"database/sql"
"log"
"net"
pb "github.com/example/user/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type server struct {
pb.UnimplementedUserServiceServer
db *sql.DB
}
func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user := &pb.User{}
err := s.db.QueryRowContext(ctx,
"SELECT id, name, email FROM users WHERE id = $1",
req.GetId(),
).Scan(&user.Id, &user.Name, &user.Email)
if err == sql.ErrNoRows {
return nil, status.Errorf(codes.NotFound, "user not found")
}
if err != nil {
return nil, status.Errorf(codes.Internal, "database error")
}
return user, nil
}
func (s *server) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
user := &pb.User{
Name: req.GetName(),
Email: req.GetEmail(),
}
err := s.db.QueryRowContext(ctx,
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
user.Name, user.Email,
).Scan(&user.Id)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to create user")
}
return user, nil
}
func (s *server) ListUsers(ctx context.Context, req *pb.ListUsersRequest) (*pb.ListUsersResponse, error) {
rows, err := s.db.QueryContext(ctx,
"SELECT id, name, email FROM users LIMIT $1 OFFSET $2",
req.GetLimit(), req.GetOffset())
if err != nil {
return nil, status.Errorf(codes.Internal, "database error")
}
defer rows.Close()
var users []*pb.User
for rows.Next() {
user := &pb.User{}
if err := rows.Scan(&user.Id, &user.Name, &user.Email); err != nil {
return nil, status.Errorf(codes.Internal, "scan error")
}
users = append(users, user)
}
return &pb.ListUsersResponse{Users: users}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
db, err := sql.Open("postgres", "...")
if err != nil {
log.Fatalf("failed to connect to database: %v", err)
}
defer db.Close()
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &server{db: db})
log.Println("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}Use Case: Microservices, efficient service-to-service communication
Key Concepts:
- Protocol Buffers for serialization
- Strongly-typed service contracts
- Error codes and status
- Efficient binary protocol
---
15. Caching Layer
Implementing caching with Redis.
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type CachedUserRepository struct {
db *sql.DB
cache *redis.Client
ttl time.Duration
}
func NewCachedUserRepository(db *sql.DB, cache *redis.Client) *CachedUserRepository {
return &CachedUserRepository{
db: db,
cache: cache,
ttl: 5 * time.Minute,
}
}
func (r *CachedUserRepository) GetUser(ctx context.Context, id int) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// Try cache first
cached, err := r.cache.Get(ctx, cacheKey).Result()
if err == nil {
var user User
if err := json.Unmarshal([]byte(cached), &user); err == nil {
return &user, nil
}
}
// Cache miss - query database
user, err := r.getUserFromDB(ctx, id)
if err != nil {
return nil, err
}
// Update cache (don't fail on cache error)
go r.cacheUser(context.Background(), cacheKey, user)
return user, nil
}
func (r *CachedUserRepository) getUserFromDB(ctx context.Context, id int) (*User, error) {
user := &User{}
err := r.db.QueryRowContext(ctx,
"SELECT id, name, email FROM users WHERE id = $1",
id).Scan(&user.ID, &user.Name, &user.Email)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user not found")
}
return user, err
}
func (r *CachedUserRepository) cacheUser(ctx context.Context, key string, user *User) {
data, err := json.Marshal(user)
if err != nil {
return
}
r.cache.Set(ctx, key, data, r.ttl)
}
func (r *CachedUserRepository) UpdateUser(ctx context.Context, user *User) error {
// Update database
_, err := r.db.ExecContext(ctx,
"UPDATE users SET name = $1, email = $2 WHERE id = $3",
user.Name, user.Email, user.ID)
if err != nil {
return err
}
// Invalidate cache
cacheKey := fmt.Sprintf("user:%d", user.ID)
r.cache.Del(ctx, cacheKey)
return nil
}
// Cache-aside pattern with loader function
func (r *CachedUserRepository) GetOrLoad(ctx context.Context, id int,
loader func(context.Context, int) (*User, error)) (*User, error) {
cacheKey := fmt.Sprintf("user:%d", id)
// Try cache
cached, err := r.cache.Get(ctx, cacheKey).Result()
if err == nil {
var user User
if err := json.Unmarshal([]byte(cached), &user); err == nil {
return &user, nil
}
}
// Load from source
user, err := loader(ctx, id)
if err != nil {
return nil, err
}
// Cache result
go r.cacheUser(context.Background(), cacheKey, user)
return user, nil
}
func main() {
// Database connection
db, err := sql.Open("postgres", "...")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Redis connection
cache := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
defer cache.Close()
repo := NewCachedUserRepository(db, cache)
ctx := context.Background()
// Get user (cached)
user, err := repo.GetUser(ctx, 1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %+v\n", user)
}Use Case: Performance optimization, reducing database load
Key Concepts:
- Cache-aside pattern
- TTL for cache expiration
- Cache invalidation on updates
- Async cache population
---
16. Event-Driven Architecture
Publishing and consuming events with message queue.
package main
import (
"context"
"encoding/json"
"log"
"time"
"github.com/streadway/amqp"
)
type Event struct {
Type string `json:"type"`
Timestamp time.Time `json:"timestamp"`
Data map[string]interface{} `json:"data"`
}
type EventPublisher struct {
conn *amqp.Connection
channel *amqp.Channel
}
func NewEventPublisher(url string) (*EventPublisher, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, err
}
channel, err := conn.Channel()
if err != nil {
return nil, err
}
// Declare exchange
err = channel.ExchangeDeclare(
"events", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
if err != nil {
return nil, err
}
return &EventPublisher{conn: conn, channel: channel}, nil
}
func (p *EventPublisher) Publish(ctx context.Context, event *Event) error {
event.Timestamp = time.Now()
body, err := json.Marshal(event)
if err != nil {
return err
}
return p.channel.PublishWithContext(
ctx,
"events", // exchange
event.Type, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "application/json",
Body: body,
},
)
}
func (p *EventPublisher) Close() {
p.channel.Close()
p.conn.Close()
}
type EventHandler func(Event) error
type EventConsumer struct {
conn *amqp.Connection
channel *amqp.Channel
handlers map[string]EventHandler
}
func NewEventConsumer(url string) (*EventConsumer, error) {
conn, err := amqp.Dial(url)
if err != nil {
return nil, err
}
channel, err := conn.Channel()
if err != nil {
return nil, err
}
return &EventConsumer{
conn: conn,
channel: channel,
handlers: make(map[string]EventHandler),
}, nil
}
func (c *EventConsumer) Subscribe(eventType string, handler EventHandler) error {
c.handlers[eventType] = handler
// Declare queue
q, err := c.channel.QueueDeclare(
"", // name (auto-generated)
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
return err
}
// Bind queue to exchange
err = c.channel.QueueBind(
q.Name, // queue name
eventType, // routing key
"events", // exchange
false, // no-wait
nil, // arguments
)
if err != nil {
return err
}
return nil
}
func (c *EventConsumer) Start(ctx context.Context) error {
msgs, err := c.channel.Consume(
"", // queue (will use bound queue)
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
if err != nil {
return err
}
go func() {
for {
select {
case msg := <-msgs:
var event Event
if err := json.Unmarshal(msg.Body, &event); err != nil {
log.Printf("Failed to unmarshal event: %v", err)
continue
}
if handler, ok := c.handlers[event.Type]; ok {
if err := handler(event); err != nil {
log.Printf("Handler error: %v", err)
}
}
case <-ctx.Done():
return
}
}
}()
return nil
}
func (c *EventConsumer) Close() {
c.channel.Close()
c.conn.Close()
}
func main() {
// Publisher
publisher, err := NewEventPublisher("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer publisher.Close()
// Publish event
err = publisher.Publish(context.Background(), &Event{
Type: "user.created",
Data: map[string]interface{}{
"user_id": 123,
"email": "user@example.com",
},
})
if err != nil {
log.Fatal(err)
}
// Consumer
consumer, err := NewEventConsumer("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatal(err)
}
defer consumer.Close()
// Subscribe to events
consumer.Subscribe("user.created", func(event Event) error {
log.Printf("User created: %+v", event.Data)
return nil
})
// Start consuming
ctx := context.Background()
consumer.Start(ctx)
// Keep running
select {}
}Use Case: Decoupled microservices, async processing, event sourcing
Key Concepts:
- Publish-subscribe pattern
- Event routing with topics
- Async event processing
- Decoupled services
---
17. File Upload Handler
Handling multipart file uploads.
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
)
const (
maxUploadSize = 10 * 1024 * 1024 // 10 MB
uploadPath = "./uploads"
)
func handleFileUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Limit request body size
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
// Parse multipart form
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
http.Error(w, "File too large", http.StatusBadRequest)
return
}
// Get file from form
file, fileHeader, err := r.FormFile("file")
if err != nil {
http.Error(w, "Failed to get file", http.StatusBadRequest)
return
}
defer file.Close()
// Validate file type
buffer := make([]byte, 512)
if _, err := file.Read(buffer); err != nil {
http.Error(w, "Failed to read file", http.StatusInternalServerError)
return
}
contentType := http.DetectContentType(buffer)
if contentType != "image/jpeg" && contentType != "image/png" {
http.Error(w, "Invalid file type", http.StatusBadRequest)
return
}
// Reset file pointer
file.Seek(0, 0)
// Generate unique filename
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
http.Error(w, "Failed to process file", http.StatusInternalServerError)
return
}
hashInBytes := hash.Sum(nil)[:16]
filename := hex.EncodeToString(hashInBytes) + filepath.Ext(fileHeader.Filename)
// Reset file pointer again
file.Seek(0, 0)
// Create upload directory
os.MkdirAll(uploadPath, os.ModePerm)
// Create destination file
dst, err := os.Create(filepath.Join(uploadPath, filename))
if err != nil {
http.Error(w, "Failed to create file", http.StatusInternalServerError)
return
}
defer dst.Close()
// Copy file content
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}
// Return success response
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, `{"filename": "%s", "size": %d}`, filename, fileHeader.Size)
}
// Multiple file upload
func handleMultipleFileUpload(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(maxUploadSize); err != nil {
http.Error(w, "Failed to parse form", http.StatusBadRequest)
return
}
files := r.MultipartForm.File["files"]
var uploadedFiles []string
for _, fileHeader := range files {
file, err := fileHeader.Open()
if err != nil {
continue
}
defer file.Close()
// Process each file...
filename := fileHeader.Filename
uploadedFiles = append(uploadedFiles, filename)
}
fmt.Fprintf(w, `{"uploaded": %d, "files": %v}`, len(uploadedFiles), uploadedFiles)
}
func main() {
http.HandleFunc("/upload", handleFileUpload)
http.HandleFunc("/upload-multiple", handleMultipleFileUpload)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: File upload APIs, image processing, document management
Key Concepts:
- Multipart form parsing
- File size limits
- Content type validation
- Unique filename generation
---
18. Authentication Middleware
JWT-based authentication middleware.
package main
import (
"context"
"fmt"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
)
var jwtSecret = []byte("your-secret-key")
type Claims struct {
UserID int `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
func generateToken(userID int, email string) (string, error) {
claims := Claims{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
func validateToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token")
}
type contextKey string
const userContextKey contextKey = "user"
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get token from Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
http.Error(w, "Missing authorization header", http.StatusUnauthorized)
return
}
// Extract token
parts := strings.Split(authHeader, " ")
if len(parts) != 2 || parts[0] != "Bearer" {
http.Error(w, "Invalid authorization header", http.StatusUnauthorized)
return
}
tokenString := parts[1]
// Validate token
claims, err := validateToken(tokenString)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// Add claims to context
ctx := context.WithValue(r.Context(), userContextKey, claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getUserFromContext(ctx context.Context) (*Claims, bool) {
claims, ok := ctx.Value(userContextKey).(*Claims)
return claims, ok
}
// Handler example
func protectedHandler(w http.ResponseWriter, r *http.Request) {
claims, ok := getUserFromContext(r.Context())
if !ok {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hello, %s (ID: %d)", claims.Email, claims.UserID)
}
// Login handler
func loginHandler(w http.ResponseWriter, r *http.Request) {
var creds struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
// Validate credentials (simplified)
userID := 123 // Get from database
// Generate token
token, err := generateToken(userID, creds.Email)
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(map[string]string{
"token": token,
})
}
func main() {
http.HandleFunc("/login", loginHandler)
http.Handle("/protected", authMiddleware(http.HandlerFunc(protectedHandler)))
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: API authentication, user sessions, access control
Key Concepts:
- JWT token generation and validation
- Authorization header parsing
- Context for user information
- Middleware pattern
---
19. Structured Logging
Production-ready structured logging with slog.
package main
import (
"context"
"log/slog"
"net/http"
"os"
"time"
)
// Setup logger
func setupLogger() *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
}
// Request logging middleware
func loggingMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create logger with request context
reqLogger := logger.With(
"method", r.Method,
"path", r.URL.Path,
"remote_addr", r.RemoteAddr,
"user_agent", r.UserAgent(),
)
// Add logger to context
ctx := context.WithValue(r.Context(), "logger", reqLogger)
// Wrap response writer to capture status
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// Log request
reqLogger.Info("request started")
// Process request
next.ServeHTTP(rw, r.WithContext(ctx))
// Log response
reqLogger.Info("request completed",
"status", rw.statusCode,
"duration", time.Since(start).String(),
)
})
}
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// Get logger from context
func getLogger(ctx context.Context) *slog.Logger {
if logger, ok := ctx.Value("logger").(*slog.Logger); ok {
return logger
}
return slog.Default()
}
// Example handler
func handleAPI(w http.ResponseWriter, r *http.Request) {
logger := getLogger(r.Context())
logger.Info("processing API request",
"query", r.URL.Query().Get("q"),
)
// Simulate work
time.Sleep(100 * time.Millisecond)
logger.Info("API request processed successfully")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "success"}`))
}
// Error logging example
func handleError(w http.ResponseWriter, r *http.Request) {
logger := getLogger(r.Context())
// Simulate error
err := fmt.Errorf("database connection failed")
logger.Error("operation failed",
"error", err.Error(),
"operation", "fetch_user",
"user_id", 123,
)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
func main() {
logger := setupLogger()
mux := http.NewServeMux()
mux.HandleFunc("/api", handleAPI)
mux.HandleFunc("/error", handleError)
handler := loggingMiddleware(logger)(mux)
logger.Info("server starting", "port", 8080)
log.Fatal(http.ListenAndServe(":8080", handler))
}Use Case: Production logging, debugging, monitoring
Key Concepts:
- Structured logging with slog
- Context-aware logging
- Request/response logging
- JSON output for log aggregation
---
20. Health Check System
Comprehensive health check endpoint.
package main
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"sync"
"time"
"github.com/redis/go-redis/v9"
)
type HealthStatus string
const (
StatusHealthy HealthStatus = "healthy"
StatusDegraded HealthStatus = "degraded"
StatusUnhealthy HealthStatus = "unhealthy"
)
type ComponentHealth struct {
Status HealthStatus `json:"status"`
Message string `json:"message,omitempty"`
Latency string `json:"latency,omitempty"`
}
type HealthResponse struct {
Status HealthStatus `json:"status"`
Timestamp time.Time `json:"timestamp"`
Components map[string]ComponentHealth `json:"components"`
}
type HealthChecker struct {
db *sql.DB
cache *redis.Client
}
func NewHealthChecker(db *sql.DB, cache *redis.Client) *HealthChecker {
return &HealthChecker{db: db, cache: cache}
}
func (h *HealthChecker) checkDatabase(ctx context.Context) ComponentHealth {
start := time.Now()
err := h.db.PingContext(ctx)
latency := time.Since(start)
if err != nil {
return ComponentHealth{
Status: StatusUnhealthy,
Message: err.Error(),
Latency: latency.String(),
}
}
if latency > 1*time.Second {
return ComponentHealth{
Status: StatusDegraded,
Message: "High latency",
Latency: latency.String(),
}
}
return ComponentHealth{
Status: StatusHealthy,
Latency: latency.String(),
}
}
func (h *HealthChecker) checkCache(ctx context.Context) ComponentHealth {
start := time.Now()
err := h.cache.Ping(ctx).Err()
latency := time.Since(start)
if err != nil {
return ComponentHealth{
Status: StatusUnhealthy,
Message: err.Error(),
Latency: latency.String(),
}
}
return ComponentHealth{
Status: StatusHealthy,
Latency: latency.String(),
}
}
func (h *HealthChecker) Check(ctx context.Context) *HealthResponse {
var wg sync.WaitGroup
components := make(map[string]ComponentHealth)
var mu sync.Mutex
// Check database
wg.Add(1)
go func() {
defer wg.Done()
health := h.checkDatabase(ctx)
mu.Lock()
components["database"] = health
mu.Unlock()
}()
// Check cache
wg.Add(1)
go func() {
defer wg.Done()
health := h.checkCache(ctx)
mu.Lock()
components["cache"] = health
mu.Unlock()
}()
wg.Wait()
// Determine overall status
overallStatus := StatusHealthy
for _, comp := range components {
if comp.Status == StatusUnhealthy {
overallStatus = StatusUnhealthy
break
}
if comp.Status == StatusDegraded && overallStatus != StatusUnhealthy {
overallStatus = StatusDegraded
}
}
return &HealthResponse{
Status: overallStatus,
Timestamp: time.Now(),
Components: components,
}
}
func healthHandler(checker *HealthChecker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
health := checker.Check(ctx)
w.Header().Set("Content-Type", "application/json")
// Set HTTP status based on health
switch health.Status {
case StatusHealthy:
w.WriteHeader(http.StatusOK)
case StatusDegraded:
w.WriteHeader(http.StatusOK)
case StatusUnhealthy:
w.WriteHeader(http.StatusServiceUnavailable)
}
json.NewEncoder(w).Encode(health)
}
}
// Simple liveness probe
func livenessHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
// Readiness probe
func readinessHandler(checker *HealthChecker) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
// Check critical dependencies only
dbHealth := checker.checkDatabase(ctx)
if dbHealth.Status == StatusUnhealthy {
w.WriteHeader(http.StatusServiceUnavailable)
w.Write([]byte("NOT READY"))
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("READY"))
}
}
func main() {
db, _ := sql.Open("postgres", "...")
cache := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
checker := NewHealthChecker(db, cache)
http.HandleFunc("/health", healthHandler(checker))
http.HandleFunc("/health/live", livenessHandler)
http.HandleFunc("/health/ready", readinessHandler(checker))
log.Fatal(http.ListenAndServe(":8080", nil))
}Use Case: Kubernetes probes, monitoring, service health
Key Concepts:
- Component health checks
- Liveness and readiness probes
- Concurrent health checks
- Status aggregation
---
21-25: Additional Examples
Due to length constraints, here are brief outlines for the remaining examples:
21. Concurrent File Processing
- Walk directory tree
- Process files in worker pool
- Aggregate results with channels
- Progress tracking
22. Service Discovery
- Service registration
- Health checking
- Load balancing
- Failover handling
23. Message Queue Consumer
- AMQP/RabbitMQ consumer
- Message acknowledgment
- Retry logic
- Dead letter queue
24. Background Job Processor
- Job queue with Redis
- Worker pool
- Job retry and exponential backoff
- Job status tracking
25. API Gateway Pattern
- Request routing
- Service discovery integration
- Load balancing
- Response aggregation
---
Version: 1.0.0 Last Updated: October 2025 Total Examples: 25+ production-ready patterns
Go Backend Development Skill
Complete guide for building production-grade backend systems with Go, emphasizing concurrency patterns, web servers, and microservices architecture.
Overview
This skill provides comprehensive guidance for developing backend applications with Go (Golang), covering everything from basic HTTP servers to advanced concurrent systems. Go is particularly well-suited for backend development due to its:
- Built-in Concurrency: Goroutines and channels make concurrent programming natural and efficient
- Fast Compilation: Near-instant builds enable rapid development cycles
- Static Typing: Catch errors at compile time while maintaining clean syntax
- Standard Library: Robust HTTP, networking, and encoding packages included
- Single Binary Deployment: Simple distribution and deployment
- Cross-Platform: Compile for any OS/architecture from any platform
- Memory Efficiency: Efficient garbage collection with low overhead
- Performance: Near C/C++ performance for many workloads
Why Go for Backend Development
Performance Benefits
Go consistently delivers excellent performance for backend workloads:
- Fast Startup: Applications start in milliseconds
- Low Memory Footprint: Typical Go services use 10-50MB RAM
- High Throughput: Handle 10,000+ concurrent connections per instance
- Efficient Networking: Non-blocking I/O built into the runtime
- Quick Compilation: Rebuild entire applications in seconds
Concurrency Model
Go's concurrency model is its defining feature:
// Launch thousands of concurrent operations effortlessly
for i := 0; i < 10000; i++ {
go handleRequest(requests[i])
}Key Advantages:
- Goroutines cost ~2KB each (vs. 1MB+ for OS threads)
- Communicate safely through channels (no manual locking)
- Built-in scheduler manages goroutines efficiently
- Context propagation for cancellation and timeouts
Developer Experience
Simplicity:
- 25 keywords (vs. 50+ in most languages)
- One formatting style enforced by
gofmt - Explicit error handling (no hidden exceptions)
- Straightforward dependency management with modules
Tooling:
go test- built-in testing and benchmarkinggo build- compile for any platformgo mod- dependency managementgo vet- static analysisgo fmt- automatic formattingpprof- integrated profiling- Race detector for finding concurrency bugs
When to Choose Go
Excellent For
1. Web Services and APIs
- RESTful APIs
- GraphQL servers
- WebSocket servers
- Microservices
2. Network Services
- Proxies and load balancers
- Service meshes
- DNS servers
- TCP/UDP servers
3. Cloud-Native Applications
- Kubernetes operators
- Container tools (Docker, containerd)
- Service discovery
- Configuration management
4. Data Processing
- Stream processing
- Log aggregation
- ETL pipelines
- Real-time analytics
5. CLI Tools
- System utilities
- DevOps tools
- Database clients
- Automation scripts
Consider Alternatives For
- CPU-intensive scientific computing (consider Rust, C++)
- Machine learning inference at scale (Python, C++)
- Desktop GUI applications (Electron, Qt)
- Real-time embedded systems (C, Rust)
- Applications requiring dynamic typing (Python, JavaScript)
Quick Start
Installation
macOS:
brew install goLinux:
wget https://go.dev/dl/go1.21.0.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/binWindows: Download installer from https://go.dev/dl/
Verify Installation
go version
# go version go1.21.0 darwin/arm64Create Your First Server
# Create new project
mkdir myserver
cd myserver
go mod init myserver
# Create main.go
cat > main.go << 'EOF'
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
EOF
# Run the server
go run main.goVisit http://localhost:8080 to see your server in action!
Build and Deploy
# Build for current platform
go build -o myserver
# Build for Linux
GOOS=linux GOARCH=amd64 go build -o myserver-linux
# Build optimized binary
go build -ldflags="-s -w" -o myserverCore Concepts
1. Goroutines: The Concurrency Primitive
Goroutines are the foundation of Go's concurrency model:
// Sequential execution
doExpensiveTask1()
doExpensiveTask2()
doExpensiveTask3()
// Concurrent execution
go doExpensiveTask1()
go doExpensiveTask2()
go doExpensiveTask3()Characteristics:
- Start with ~2KB stack (grows as needed)
- Scheduled by Go runtime, not OS
- Multiplexed onto OS threads
- Can have millions in a single program
2. Channels: Safe Communication
Channels provide type-safe communication between goroutines:
// Create channel
messages := make(chan string)
// Send value
go func() {
messages <- "Hello"
}()
// Receive value
msg := <-messages
fmt.Println(msg) // "Hello"Channel Patterns:
- Unbuffered: Synchronous (sender waits for receiver)
- Buffered: Asynchronous up to buffer size
- Directional: Enforce send-only or receive-only in function signatures
- Select: Multiplex multiple channel operations
3. The Select Statement
Select enables handling multiple channel operations:
select {
case msg := <-messages:
fmt.Println("Received:", msg)
case <-timeout:
fmt.Println("Timed out")
case <-done:
return
default:
fmt.Println("No message ready")
}4. Context: Request Lifecycle Management
Context propagates cancellation signals and deadlines:
func processRequest(ctx context.Context, data string) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
select {
case result := <-processAsync(data):
return nil
case <-ctx.Done():
return ctx.Err() // Timeout or cancellation
}
}5. Error Handling
Go uses explicit error handling:
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
// Use resultBest Practices:
- Always check errors immediately
- Wrap errors with context using
%w - Return errors rather than panic
- Use custom error types for programmatic handling
Project Structure
Standard Go project layout:
myproject/
├── cmd/
│ └── server/
│ └── main.go # Application entrypoint
├── internal/
│ ├── api/
│ │ ├── handlers.go # HTTP handlers
│ │ └── middleware.go # HTTP middleware
│ ├── service/
│ │ └── user.go # Business logic
│ ├── repository/
│ │ └── postgres.go # Data access
│ └── models/
│ └── user.go # Domain models
├── pkg/
│ └── utils/
│ └── validation.go # Public utilities
├── api/
│ └── openapi.yaml # API specification
├── migrations/
│ └── 001_init.sql # Database migrations
├── scripts/
│ └── build.sh # Build scripts
├── configs/
│ └── config.yaml # Configuration
├── docker/
│ └── Dockerfile # Container definition
├── go.mod # Module definition
├── go.sum # Dependency checksums
└── README.mdKey Directories:
cmd/: Main applications for this projectinternal/: Private application code (cannot be imported by other projects)pkg/: Public library code (can be imported by other projects)api/: API definitions and protocolsmigrations/: Database schema migrations
Development Workflow
1. Initialize Project
mkdir myproject && cd myproject
go mod init github.com/username/myproject2. Add Dependencies
go get github.com/gorilla/mux
go get github.com/lib/pq3. Write Code
# Run during development
go run cmd/server/main.go
# Watch for changes (use air or similar)
air4. Test
# Run all tests
go test ./...
# Run with coverage
go test -cover ./...
# Run with race detector
go test -race ./...
# Benchmark
go test -bench . ./...5. Format and Lint
# Format code
go fmt ./...
# Vet code
go vet ./...
# Use staticcheck
staticcheck ./...6. Build
# Build for current platform
go build -o bin/server cmd/server/main.go
# Build for production (optimized)
go build -ldflags="-s -w" -o bin/server cmd/server/main.goCommon Patterns
HTTP Server
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response struct {
Message string `json:"message"`
}
func main() {
http.HandleFunc("/api/hello", handleHello)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleHello(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{Message: "Hello, World!"})
}Database Connection
import (
"database/sql"
_ "github.com/lib/pq"
)
func initDB() (*sql.DB, error) {
db, err := sql.Open("postgres",
"host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable")
if err != nil {
return nil, err
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(5)
db.SetConnMaxLifetime(5 * time.Minute)
return db, db.Ping()
}Middleware
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))
})
}
// Usage
http.Handle("/api/", loggingMiddleware(apiHandler))Worker Pool
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
results <- process(j)
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
// Start workers
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// Send jobs
for j := 1; j <= 9; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= 9; a++ {
<-results
}
}Learning Path
Beginner (Week 1-2)
1. Install Go and set up environment 2. Learn basic syntax and types 3. Understand functions and methods 4. Work with slices, maps, and structs 5. Build simple CLI tools
Intermediate (Week 3-4)
1. Master goroutines and channels 2. Understand the select statement 3. Build HTTP servers 4. Work with JSON and APIs 5. Implement error handling patterns
Advanced (Week 5-8)
1. Deep dive into concurrency patterns 2. Master context package 3. Implement middleware patterns 4. Database integration with connection pooling 5. Testing and benchmarking 6. Profiling and optimization 7. Build microservices 8. Deploy to production
Production Checklist
Before Deploying
- [ ] All tests pass (
go test ./...) - [ ] No race conditions (
go test -race ./...) - [ ] Code formatted (
go fmt ./...) - [ ] Code vetted (
go vet ./...) - [ ] Dependencies up to date (
go mod tidy) - [ ] Configuration externalized (environment variables)
- [ ] Logging implemented (structured logging)
- [ ] Metrics exposed (Prometheus format)
- [ ] Health check endpoint (
/health) - [ ] Graceful shutdown implemented
- [ ] Request timeouts configured
- [ ] Database connection pool tuned
- [ ] TLS certificates configured (for HTTPS)
- [ ] Rate limiting implemented
- [ ] Input validation on all endpoints
- [ ] Error handling doesn't leak sensitive info
Deployment Best Practices
1. Build optimized binaries
CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o app2. Use minimal Docker images
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o server cmd/server/main.go
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
CMD ["./server"]3. Set resource limits
- Memory: Set
GOMEMLIMIT - CPU: Configure container limits
- File descriptors: Increase for high-concurrency
4. Monitor in production
- CPU and memory usage
- Goroutine count
- Request rate and latency
- Error rates
- Database connection pool stats
Common Gotchas
1. Loop Variable Capture
// Wrong - all goroutines share same variable
for _, v := range values {
go func() {
fmt.Println(v) // Unpredictable output
}()
}
// Correct - pass variable as parameter
for _, v := range values {
go func(val string) {
fmt.Println(val) // Correct output
}(v)
}2. Nil Maps
var m map[string]int
m["key"] = 1 // Panic! Map is nil
// Correct
m := make(map[string]int)
m["key"] = 1 // Works3. Channel Deadlocks
// Deadlock - unbuffered channel with no receiver
ch := make(chan int)
ch <- 1 // Blocks forever
// Fix 1: Use buffered channel
ch := make(chan int, 1)
ch <- 1 // Doesn't block
// Fix 2: Receive in goroutine
go func() {
<-ch
}()
ch <- 14. Goroutine Leaks
// Leak - goroutine never exits
func leak() {
ch := make(chan int)
go func() {
val := <-ch // Waits forever
process(val)
}()
return // Function exits, goroutine remains
}
// Fix - use context for cancellation
func noLeak(ctx context.Context) {
ch := make(chan int)
go func() {
select {
case val := <-ch:
process(val)
case <-ctx.Done():
return
}
}()
}Resources
Official
Learning
Community
Tools
---
Version: 1.0.0 Last Updated: October 2025 Maintained By: Claude Code Skills Team
Related skills
Forks & variants (1)
Golang Backend Development has 1 known copy in the catalog totaling 17 installs. They canonicalize to this original listing.
- manutej - 17 installs
How it compares
Pick golang-backend-development over generic language tutorials when you need copy-ready Go service patterns for HTTP, concurrency, and databases.
FAQ
What does golang-backend-development do?
Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
When should I use golang-backend-development?
Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
What are common prerequisites?
--- name: golang-backend-development description: Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment tags: [golang, go, c
Is Golang Backend Development safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.