Go Context
In Go, the context package provides a way to carry deadlines, cancellations, and request-scoped values across API boundaries and between processes. Understanding context is crucial for writing robust, efficient web services with Gin.
What is Context?
Context in Go is a built-in package that helps you manage the lifetime of operations, especially in concurrent programs. A Context is an interface that contains:
- Deadline information (when the context should expire)
 - Cancellation signals
 - Request-scoped key-value pairs
 
Context is particularly important in web applications like those built with Gin, where you need to handle request timeouts, cancel operations, and pass request-specific data through your application.
Why Context is Important
- Resource Management: Prevents resource leaks by signaling when operations should be abandoned
 - Timeout Control: Allows you to set time limits on operations
 - Cancellation Propagation: Enables cancellation signals to flow through your program
 - Request-Scoped Values: Carries request-specific data through your application
 - Clean API Design: Avoids parameter bloat in function signatures
 
Basic Context Usage
Let's start with the basic usage of context in Go:
package main
import (
    "context"
    "fmt"
    "time"
)
func main() {
    // Create a background context as the root context
    ctx := context.Background()
    
    // Derive a context with cancellation capability
    ctx, cancel := context.WithCancel(ctx)
    
    // Don't forget to call cancel when you're done
    defer cancel()
    
    // Start a goroutine that uses the context
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("Work cancelled, cleaning up...")
                return
            default:
                fmt.Println("Doing work...")
                time.Sleep(500 * time.Millisecond)
            }
        }
    }()
    
    // Let it work for a while
    time.Sleep(2 * time.Second)
    
    // Cancel the context - this will signal the goroutine to stop
    fmt.Println("Cancelling work...")
    cancel()
    
    // Give the goroutine time to clean up
    time.Sleep(1 * time.Second)
}
Output:
Doing work...
Doing work...
Doing work...
Doing work...
Cancelling work...
Work cancelled, cleaning up...
Context in Gin Framework
Gin has built-in context support through its own gin.Context type, which wraps the standard context.Context. In Gin handlers, you have access to both.
Accessing the Context in Gin
package main
import (
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)
func main() {
    r := gin.Default()
    
    r.GET("/ping", func(c *gin.Context) {
        // Get the underlying context.Context
        ctx := c.Request.Context()
        
        // You can use both c (gin.Context) and ctx (context.Context)
        
        // Using gin.Context
        c.JSON(http.StatusOK, gin.H{
            "message": "pong",
        })
    })
    
    r.Run(":8080")
}
Context Timeout
One common use of context is for timeouts. Here's how to implement a timeout in a Gin handler:
package main
import (
    "context"
    "github.com/gin-gonic/gin"
    "net/http"
    "time"
)
func main() {
    r := gin.Default()
    
    r.GET("/slow-operation", func(c *gin.Context) {
        // Create a context with a 2-second timeout
        ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
        defer cancel()
        
        // Channel to receive result
        resultChan := make(chan string, 1)
        
        // Run the slow operation in a separate goroutine
        go func() {
            result := performSlowOperation()
            resultChan <- result
        }()
        
        // Wait for either the operation to complete or the context to timeout
        select {
        case result := <-resultChan:
            c.JSON(http.StatusOK, gin.H{"result": result})
        case <-ctx.Done():
            c.JSON(http.StatusRequestTimeout, gin.H{"error": "operation timed out"})
        }
    })
    
    r.Run(":8080")
}
func performSlowOperation() string {
    // Simulate a slow operation
    time.Sleep(3 * time.Second)
    return "Operation completed"
}
When you call this endpoint, it will return a timeout error after 2 seconds, even though the operation takes 3 seconds.
Context with Value
Context allows you to store and retrieve request-scoped values:
package main
import (
    "context"
    "github.com/gin-gonic/gin"
    "net/http"
)
// Define key types for type safety
type contextKey string
const userIDKey contextKey = "userID"
func authMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // In a real app, you would extract this from JWT or session
        userID := "user-123" 
        
        // Create a new context with the user ID
        ctx := context.WithValue(c.Request.Context(), userIDKey, userID)
        
        // Update the request with the new context
        c.Request = c.Request.WithContext(ctx)
        
        c.Next()
    }
}
func main() {
    r := gin.Default()
    
    // Use the authentication middleware
    r.Use(authMiddleware())
    
    r.GET("/profile", func(c *gin.Context) {
        // Get the user ID from the context
        ctx := c.Request.Context()
        userID, ok := ctx.Value(userIDKey).(string)
        
        if !ok {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "userID not found in context"})
            return
        }
        
        // Use the userID to fetch user data (simulated here)
        userData := fetchUserData(userID)
        
        c.JSON(http.StatusOK, userData)
    })
    
    r.Run(":8080")
}
func fetchUserData(userID string) gin.H {
    // In a real app, you would query a database
    return gin.H{
        "id": userID,
        "name": "John Doe",
        "email": "[email protected]",
    }
}
Context Cancellation in Gin
When a client disconnects, Gin automatically cancels the request context. You can listen for this cancellation:
func longRunningHandler(c *gin.Context) {
    // Get the context from the request
    ctx := c.Request.Context()
    
    // Channel for the operation result
    resultChan := make(chan gin.H, 1)
    
    // Start the operation in a goroutine
    go func() {
        // Simulate a long-running operation
        for i := 0; i < 10; i++ {
            // Check for cancellation between steps
            select {
            case <-ctx.Done():
                fmt.Println("Operation cancelled by client")
                return
            default:
                // Continue with the operation
                time.Sleep(500 * time.Millisecond)
                fmt.Println("Processing step", i+1)
            }
        }
        
        resultChan <- gin.H{"status": "completed"}
    }()
    
    // Wait for either completion or cancellation
    select {
    case result := <-resultChan:
        c.JSON(http.StatusOK, result)
    case <-ctx.Done():
        // The client disconnected
        c.AbortWithStatusJSON(http.StatusGone, gin.H{
            "error": "client disconnected",
        })
    }
}
Best Practices for Working with Context
- 
Always pass context as the first parameter in functions that perform I/O operations:
func fetchUserData(ctx context.Context, userID string) (*User, error) {
// Use the context for timeouts, cancellation, etc.
} - 
Don't store context in struct fields - pass it explicitly as a parameter:
// Incorrect
type Service struct {
ctx context.Context
}
// Correct
type Service struct {
// No context here
}
func (s *Service) DoSomething(ctx context.Context) error {
// Use the context here
} - 
Use context for cancellation, not for flow control:
// Avoid this pattern
if ctx.Value("admin").(bool) {
// Admin-specific logic
}
// Better approach
func handleRequest(ctx context.Context, user User) {
if user.IsAdmin {
// Admin-specific logic
}
} - 
Be cautious with context.Value(). Use it primarily for request-scoped values, not for passing function parameters.
 - 
Always call cancel functions that are returned from context creation to avoid resource leaks.
 
Real-world Example: Database Query with Timeout
Here's a complete example showing how to use context with a database query in Gin:
package main
import (
    "context"
    "database/sql"
    "github.com/gin-gonic/gin"
    _ "github.com/go-sql-driver/mysql"
    "net/http"
    "time"
)
type Product struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Price float64 `json:"price"`
}
func main() {
    // Connect to database
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/store")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    r := gin.Default()
    
    r.GET("/products/:id", func(c *gin.Context) {
        // Get product ID from URL parameter
        idStr := c.Param("id")
        
        // Create a context with a 3-second timeout
        ctx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
        defer cancel()
        
        // Query the database with the context
        product, err := getProduct(ctx, db, idStr)
        if err != nil {
            if err == context.DeadlineExceeded {
                c.JSON(http.StatusGatewayTimeout, gin.H{"error": "database query timed out"})
            } else if err == sql.ErrNoRows {
                c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
            } else {
                c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            }
            return
        }
        
        c.JSON(http.StatusOK, product)
    })
    
    r.Run(":8080")
}
func getProduct(ctx context.Context, db *sql.DB, id string) (*Product, error) {
    var product Product
    
    // Use QueryRowContext instead of QueryRow to respect the context
    err := db.QueryRowContext(
        ctx,
        "SELECT id, name, price FROM products WHERE id = ?",
        id,
    ).Scan(&product.ID, &product.Name, &product.Price)
    
    if err != nil {
        return nil, err
    }
    
    return &product, nil
}
Summary
Context in Go is a powerful mechanism for controlling concurrency and managing the lifecycle of operations. In Gin applications, it's essential for:
- Setting timeouts for requests
 - Handling client disconnections gracefully
 - Carrying request-scoped values (like user IDs and authentication info)
 - Cancelling operations when they're no longer needed
 
By understanding and correctly using context, you can build more robust and responsive web applications with Go and Gin.
Further Resources
- Context package documentation
 - Go Blog: Go Concurrency Patterns: Context
 - Gin Documentation on Context
 
Exercises
- 
Create a Gin endpoint that fetches data from two different services and returns an error if either takes more than 2 seconds.
 - 
Implement a middleware that adds user information to the context after verifying a JWT token.
 - 
Create a long-polling endpoint that uses context to detect when a client disconnects.
 - 
Build a service that performs batch processing but can be cancelled via the context if the user navigates away from the page.
 - 
Create an endpoint that propagates the context to multiple goroutines and ensures all of them stop if the context is cancelled.
 
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!