Web Development

Web Development Fundamentals

Go Web Development Ecosystem

Go provides excellent built-in support for web development with the net/http package, along with a rich ecosystem of frameworks and libraries for building scalable web applications and APIs.

Go Web Development Stack HTTP Foundation (net/http) Request/Response Routing Handlers Middleware Static Files Web Frameworks • Gin (Fast) • Echo (Feature-rich) • Fiber (Express-like) Middleware • Authentication • Logging & Metrics • CORS & Security API Tools • REST APIs • GraphQL • gRPC HTTP Request Flow Client Request Router Middleware Handler Response Common Patterns MVC Pattern Models • Views Controllers API First JSON APIs RESTful Design Microservices Service Mesh Distributed Systems Full Stack Templates Server-side Rendering

HTTP Foundation

Go's net/http package provides a robust foundation for web applications with built-in support for HTTP/2, TLS, and efficient request handling.

Built-in HTTP/2

Framework Ecosystem

Rich ecosystem of web frameworks offering different trade-offs between performance, features, and ease of use for various application needs.

Gin Echo Fiber

Performance & Scalability

Go's concurrency model and efficient runtime make it ideal for building high-performance web services that can handle thousands of concurrent requests.

Concurrent Scalable

Basic HTTP Server

Create a web server using Go's net/http package.

package main

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
)

func main() {
    // Handle routes
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/about", aboutHandler)
    http.HandleFunc("/api/users", usersAPIHandler)
    
    // Serve static files
    fs := http.FileServer(http.Dir("./static"))
    http.Handle("/static/", http.StripPrefix("/static/", fs))
    
    log.Println("Server starting on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    fmt.Fprintf(w, "

Welcome to Go Web Server

") } func aboutHandler(w http.ResponseWriter, r *http.Request) { data := struct { Title string Message string }{ Title: "About Us", Message: "Built with Go", } tmpl := template.Must(template.New("about").Parse(`

{{.Title}}

{{.Message}}

`)) tmpl.Execute(w, data) } func usersAPIHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.Method { case http.MethodGet: fmt.Fprint(w, `{"users": [{"id": 1, "name": "John"}]}`) case http.MethodPost: // Handle POST request default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } }

Gin Web Framework

Build robust web applications using the Gin framework.

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

type Todo struct {
    ID        string `json:"id"`
    Title     string `json:"title" binding:"required"`
    Completed bool   `json:"completed"`
}

var todos = []Todo{
    {ID: "1", Title: "Learn Go", Completed: false},
    {ID: "2", Title: "Build API", Completed: false},
}

func main() {
    router := gin.Default()
    
    // Middleware
    router.Use(gin.Logger())
    router.Use(gin.Recovery())
    
    // Static files
    router.Static("/assets", "./assets")
    router.LoadHTMLGlob("templates/*")
    
    // HTML rendering
    router.GET("/", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title": "Home Page",
            "todos": todos,
        })
    })
    
    // API routes
    api := router.Group("/api")
    {
        api.GET("/todos", getTodos)
        api.GET("/todos/:id", getTodoByID)
        api.POST("/todos", createTodo)
        api.PUT("/todos/:id", updateTodo)
        api.DELETE("/todos/:id", deleteTodo)
    }
    
    router.Run(":8080")
}

func getTodos(c *gin.Context) {
    c.JSON(http.StatusOK, todos)
}

func getTodoByID(c *gin.Context) {
    id := c.Param("id")
    
    for _, todo := range todos {
        if todo.ID == id {
            c.JSON(http.StatusOK, todo)
            return
        }
    }
    
    c.JSON(http.StatusNotFound, gin.H{"error": "Todo not found"})
}

func createTodo(c *gin.Context) {
    var newTodo Todo
    
    if err := c.ShouldBindJSON(&newTodo); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    todos = append(todos, newTodo)
    c.JSON(http.StatusCreated, newTodo)
}

Template Rendering

Render dynamic HTML templates with Go's template engine.

// templates/layout.html
{{define "layout"}}



    {{.Title}}
    


        
    
{{template "content" .}}
{{end}} // templates/home.html {{template "layout" .}} {{define "content"}}

Welcome {{.User.Name}}

{{range .Products}}

{{.Name}}

Price: ${{.Price}}

{{end}}
{{end}}
package main

import (
    "html/template"
    "net/http"
    "path/filepath"
)

type PageData struct {
    Title    string
    User     User
    Products []Product
}

func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
    templates := template.Must(template.ParseGlob("templates/*.html"))
    
    err := templates.ExecuteTemplate(w, tmpl, data)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
    data := PageData{
        Title: "Home",
        User:  User{Name: "John Doe"},
        Products: []Product{
            {ID: "1", Name: "Laptop", Price: 999.99},
            {ID: "2", Name: "Mouse", Price: 29.99},
        },
    }
    
    renderTemplate(w, "home.html", data)
}

Middleware Implementation

Create reusable middleware for cross-cutting concerns.

package middleware

import (
    "log"
    "net/http"
    "time"
    "github.com/golang-jwt/jwt/v4"
)

// Logging middleware
func LoggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Wrap ResponseWriter to capture status
        wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        
        next.ServeHTTP(wrapped, r)
        
        log.Printf(
            "%s %s %d %v",
            r.Method,
            r.RequestURI,
            wrapped.statusCode,
            time.Since(start),
        )
    }
}

// CORS middleware
func CORSMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return 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 == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    }
}

// Auth middleware
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        tokenString := r.Header.Get("Authorization")
        if tokenString == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
            return []byte("secret"), nil
        })
        
        if err != nil || !token.Valid {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        next.ServeHTTP(w, r)
    }
}

// Chain middleware
func Chain(h http.HandlerFunc, middleware ...func(http.HandlerFunc) http.HandlerFunc) http.HandlerFunc {
    for _, m := range middleware {
        h = m(h)
    }
    return h
}

WebSocket Implementation

Real-time communication with WebSockets.

package main

import (
    "log"
    "net/http"
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true // Allow all origins in dev
    },
}

type Hub struct {
    clients    map[*Client]bool
    broadcast  chan []byte
    register   chan *Client
    unregister chan *Client
}

type Client struct {
    hub  *Hub
    conn *websocket.Conn
    send chan []byte
}

func newHub() *Hub {
    return &Hub{
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
        clients:    make(map[*Client]bool),
    }
}

func (h *Hub) run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
            log.Println("Client connected")
            
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
                log.Println("Client disconnected")
            }
            
        case message := <-h.broadcast:
            for client := range h.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.clients, client)
                }
            }
        }
    }
}

func (c *Client) readPump() {
    defer func() {
        c.hub.unregister <- c
        c.conn.Close()
    }()
    
    for {
        _, message, err := c.conn.ReadMessage()
        if err != nil {
            break
        }
        c.hub.broadcast <- message
    }
}

func (c *Client) writePump() {
    defer c.conn.Close()
    
    for {
        select {
        case message, ok := <-c.send:
            if !ok {
                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }
            c.conn.WriteMessage(websocket.TextMessage, message)
        }
    }
}

func wsHandler(hub *Hub, w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    
    client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
    client.hub.register <- client
    
    go client.writePump()
    go client.readPump()
}

Session Management

Implement secure session handling for user authentication.

package main

import (
    "github.com/gorilla/sessions"
    "net/http"
)

var store = sessions.NewCookieStore([]byte("secret-key"))

func init() {
    store.Options = &sessions.Options{
        Path:     "/",
        MaxAge:   86400 * 7, // 7 days
        HttpOnly: true,
        Secure:   true, // Set to true in production with HTTPS
        SameSite: http.SameSiteLaxMode,
    }
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := store.Get(r, "session")
    
    // Authenticate user
    username := r.FormValue("username")
    password := r.FormValue("password")
    
    if authenticateUser(username, password) {
        session.Values["authenticated"] = true
        session.Values["username"] = username
        session.Save(r, w)
        
        http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
    } else {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
    }
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    session, _ := store.Get(r, "session")
    session.Values["authenticated"] = false
    session.Save(r, w)
    
    http.Redirect(w, r, "/", http.StatusSeeOther)
}

func requireAuth(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        session, _ := store.Get(r, "session")
        
        auth, ok := session.Values["authenticated"].(bool)
        if !ok || !auth {
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }
        
        next.ServeHTTP(w, r)
    }
}

File Upload Handling

Handle file uploads with validation and storage.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "path/filepath"
)

const maxUploadSize = 10 << 20 // 10 MB

func uploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    // Parse multipart form
    r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
    if err := r.ParseMultipartForm(maxUploadSize); err != nil {
        http.Error(w, "File too large", http.StatusBadRequest)
        return
    }
    
    file, header, err := r.FormFile("file")
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    defer file.Close()
    
    // Validate file type
    buff := make([]byte, 512)
    _, err = file.Read(buff)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    filetype := http.DetectContentType(buff)
    if filetype != "image/jpeg" && filetype != "image/png" {
        http.Error(w, "Invalid file type", http.StatusBadRequest)
        return
    }
    
    // Reset file pointer
    file.Seek(0, io.SeekStart)
    
    // Create upload directory
    uploadDir := "./uploads"
    os.MkdirAll(uploadDir, os.ModePerm)
    
    // Generate unique filename
    filename := fmt.Sprintf("%d_%s", time.Now().Unix(), header.Filename)
    filepath := filepath.Join(uploadDir, filename)
    
    // Save file
    dst, err := os.Create(filepath)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer dst.Close()
    
    _, err = io.Copy(dst, file)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    fmt.Fprintf(w, "File uploaded successfully: %s", filename)
}

Advanced Web Development Patterns

Production-ready patterns for building scalable and secure web applications.

// Advanced middleware chaining with context
type Middleware func(http.Handler) http.Handler

func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

// Rate limiting middleware with token bucket
func RateLimitMiddleware(requests int, window time.Duration) Middleware {
    limiter := rate.NewLimiter(rate.Every(window/time.Duration(requests)), requests)
    
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

// Circuit breaker pattern
type CircuitBreaker struct {
    maxFailures int
    timeout     time.Duration
    failures    int
    lastFail    time.Time
    state       string // "closed", "open", "half-open"
    mu          sync.RWMutex
}

func (cb *CircuitBreaker) Call(fn func() error) error {
    cb.mu.Lock()
    defer cb.mu.Unlock()
    
    if cb.state == "open" {
        if time.Since(cb.lastFail) > cb.timeout {
            cb.state = "half-open"
            cb.failures = 0
        } else {
            return errors.New("circuit breaker is open")
        }
    }
    
    err := fn()
    if err != nil {
        cb.failures++
        cb.lastFail = time.Now()
        if cb.failures >= cb.maxFailures {
            cb.state = "open"
        }
        return err
    }
    
    cb.state = "closed"
    cb.failures = 0
    return nil
}

// Advanced error handling with context
type APIError struct {
    Code    int    \`json:"code"\`
    Message string \`json:"message"\`
    Details string \`json:"details,omitempty"\`
    TraceID string \`json:"trace_id,omitempty"\`
}

func (e APIError) Error() string {
    return fmt.Sprintf("API Error %d: %s", e.Code, e.Message)
}

func ErrorHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                traceID := r.Context().Value("trace_id").(string)
                apiErr := APIError{
                    Code:    500,
                    Message: "Internal server error",
                    TraceID: traceID,
                }
                
                w.Header().Set("Content-Type", "application/json")
                w.WriteHeader(apiErr.Code)
                json.NewEncoder(w).Encode(apiErr)
                
                log.Printf("Panic recovered: %v, Trace: %s", err, traceID)
            }
        }()
        
        next.ServeHTTP(w, r)
    })
}

// Content negotiation
func ContentNegotiation(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        accept := r.Header.Get("Accept")
        
        switch {
        case strings.Contains(accept, "application/json"):
            w.Header().Set("Content-Type", "application/json")
        case strings.Contains(accept, "application/xml"):
            w.Header().Set("Content-Type", "application/xml")
        case strings.Contains(accept, "text/html"):
            w.Header().Set("Content-Type", "text/html")
        default:
            w.Header().Set("Content-Type", "application/json")
        }
        
        next.ServeHTTP(w, r)
    })
}

Framework Comparison

Choosing the Right Web Framework

Different Go web frameworks excel in different scenarios. Here's a comprehensive comparison to help you make the right choice for your project.

Gin

Best for: High-performance APIs

  • Fastest routing performance
  • Minimal memory footprint
  • Built-in JSON validation
  • Middleware support
  • Large community
40k+ req/s Minimal

Echo

Best for: Feature-rich applications

  • Comprehensive feature set
  • Built-in middleware library
  • WebSocket support
  • Template rendering
  • Auto-generated OpenAPI
35k+ req/s Feature-rich

Fiber

Best for: Express.js developers

  • Express.js-like API
  • Fast HTTP engine (fasthttp)
  • Built-in middleware
  • WebSocket support
  • Easy migration from Node.js
45k+ req/s Express-like
Feature net/http Gin Echo Fiber
Performance (req/s) 30k+ 40k+ 35k+ 45k+
Learning Curve Medium Easy Easy Easy
Built-in Middleware Basic Good Excellent Good
JSON Validation Manual Built-in Built-in Built-in
WebSocket Support Manual Third-party Built-in Built-in
Template Engine Built-in Manual Built-in Built-in

Production Best Practices

Security

  • Always use HTTPS in production
  • Implement proper authentication & authorization
  • Validate and sanitize all input
  • Use CSRF protection
  • Set proper CORS headers
  • Rate limit API endpoints

Performance

  • Use connection pooling for databases
  • Implement caching strategies
  • Compress responses (gzip)
  • Optimize database queries
  • Use CDN for static assets
  • Monitor and profile regularly

Reliability

  • Implement circuit breakers
  • Use structured logging
  • Handle graceful shutdowns
  • Set up health check endpoints
  • Use timeout configurations
  • Implement retry mechanisms

Production Deployment Checklist

  • Configuration: Use environment variables for secrets and config
  • Logging: Structured logging with appropriate levels
  • Monitoring: Metrics, tracing, and alerting
  • Security: TLS certificates, input validation, rate limiting
  • Performance: Load testing, profiling, optimization
  • Reliability: Health checks, graceful shutdown, circuit breakers

Common Web Development Pitfalls

  • Security vulnerabilities: SQL injection, XSS, CSRF attacks
  • Performance issues: N+1 queries, memory leaks, blocking operations
  • Scalability problems: Single points of failure, resource contention
  • Error handling: Poor error messages, unhandled panics
  • Configuration management: Hardcoded values, missing environment configs

Web Development Challenges

Practical Web Development Projects

Build these projects to master Go web development from basics to advanced production systems:

1. RESTful API Server

Build a complete REST API with authentication, validation, database integration, and comprehensive test coverage.

Beginner REST

2. Real-time Chat Application

Create a WebSocket-based chat app with rooms, user management, message history, and real-time notifications.

Intermediate WebSocket

3. Microservices Architecture

Design a microservices system with service discovery, load balancing, distributed tracing, and fault tolerance.

Advanced Microservices

4. High-Performance Web Server

Build a web server handling 100k+ concurrent connections with minimal latency and memory usage.

Expert High-Performance