golang-backend-development
Complete guide for Go backend development including concurrency patterns, web servers, database integration, microservices, and production deployment
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
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, concurrency, web-servers, microservices, backend, goroutines, channels, grpc, rest-api]
tier: tier-1
---
# 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:**
```go
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:**
```go
// 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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
// 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:**
```go
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 complete
```
**Common 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:**
```go
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:**
```go
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:
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
handler := loggingMiddleware(authMiddleware(corsMiddleware(apiHandler)))
http.Handle("/api/", handler)
```
### Context in HTTP Handlers
**HTTP Request with Context:**
```go
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:**
```go
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:**
```go
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:**
```go
// 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:**
```go
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
```
**Processing Stage:**
```go
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:**
```go
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):**
```go
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:**
```go
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):**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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:
```go
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:**
```go
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:**
```go
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:
```go
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:**
```go
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:**
```go
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:**
```go
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:**
```go
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
```go
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() //