Back to Curriculum

Web Development with Go

📚 Lesson 10 of 16 ⏱️ 45 min

Web Development with Go

45 min

Go's `net/http` package provides comprehensive HTTP server and client functionality, making it easy to build web services and APIs. The package includes HTTP server, client, request/response handling, and routing capabilities. Go's standard library is powerful enough for many web applications without external frameworks. Understanding `net/http` is essential for Go web development.

Handlers process HTTP requests and responses, implementing the `http.Handler` interface with a `ServeHTTP(w http.ResponseWriter, r *http.Request)` method. Handler functions can be registered with `http.HandleFunc()` or `http.Handle()`. Handlers can access request data (headers, body, query parameters) and write responses. Understanding handlers enables you to build web endpoints.

Go supports JSON encoding/decoding through the `encoding/json` package, making it easy to build REST APIs. The `json` package can encode Go structs to JSON and decode JSON to structs using struct tags. JSON is the standard format for modern APIs. Understanding JSON handling enables you to build API endpoints that exchange structured data.

Middleware can be used to add functionality to handlers, such as logging, authentication, CORS, or request processing. Middleware functions wrap handlers, allowing you to execute code before and after handler execution. Middleware enables cross-cutting concerns to be handled consistently. Understanding middleware helps you build maintainable web applications.

Routing can be handled with the standard library or frameworks like Gorilla Mux, Gin, or Echo. The standard library provides basic routing; frameworks add features like parameter extraction, middleware chains, and route groups. Understanding routing helps you organize your web application's endpoints.

Best practices include using proper HTTP methods and status codes, handling errors appropriately, using middleware for cross-cutting concerns, validating input, and structuring handlers clearly. Web applications should be secure, performant, and maintainable. Understanding web development in Go enables you to build production-ready web services.

Key Concepts

  • net/http provides HTTP server and client functionality.
  • Handlers process HTTP requests and responses.
  • JSON encoding/decoding for API data exchange.
  • Middleware adds functionality to handlers.
  • Go's standard library is powerful for web development.

Learning Objectives

Master

  • Creating HTTP servers and handlers
  • Handling JSON encoding and decoding
  • Implementing middleware for cross-cutting concerns
  • Building RESTful APIs with Go

Develop

  • Web application architecture thinking
  • Understanding HTTP and REST principles
  • Designing maintainable web services

Tips

  • Use proper HTTP methods (GET, POST, PUT, DELETE) and status codes.
  • Handle errors appropriately and return meaningful error messages.
  • Use middleware for logging, authentication, and CORS.
  • Validate input data before processing.

Common Pitfalls

  • Not handling errors properly, causing panics.
  • Not setting proper Content-Type headers.
  • Not validating input, causing security vulnerabilities.
  • Creating handlers that are too large or do too much.

Summary

  • Go's net/http package provides powerful web development capabilities.
  • Handlers process HTTP requests and generate responses.
  • JSON encoding/decoding enables API data exchange.
  • Middleware adds cross-cutting functionality to handlers.
  • Understanding web development enables building production-ready services.

Exercise

Create a simple web server with handlers and JSON responses.

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "time"
)

// User struct for JSON
type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Created  string `json:"created"`
}

// Response struct
type Response struct {
    Success bool        `json:"success"`
    Message string      `json:"message"`
    Data    interface{} `json:"data,omitempty"`
}

// Global users slice
var users = []User{
    {ID: 1, Name: "Alice", Email: "alice@example.com", Created: time.Now().Format(time.RFC3339)},
    {ID: 2, Name: "Bob", Email: "bob@example.com", Created: time.Now().Format(time.RFC3339)},
}

// Middleware for logging
func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    }
}

// Handler for home page
func homeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "<h1>Welcome to Go Web Server</h1>")
    fmt.Fprintf(w, "<p>Available endpoints:</p>")
    fmt.Fprintf(w, "<ul>")
    fmt.Fprintf(w, "<li><a href='/api/users'>GET /api/users</a></li>")
    fmt.Fprintf(w, "<li>POST /api/users (with JSON body)</li>")
    fmt.Fprintf(w, "<li><a href='/api/users/1'>GET /api/users/{id}</a></li>")
    fmt.Fprintf(w, "</ul>")
}

// Handler for getting all users
func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    
    response := Response{
        Success: true,
        Message: "Users retrieved successfully",
        Data:    users,
    }
    
    json.NewEncoder(w).Encode(response)
}

// Handler for getting a specific user
func getUserHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    
    // Extract ID from URL
    idStr := r.URL.Path[len("/api/users/"):]
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid user ID", http.StatusBadRequest)
        return
    }
    
    // Find user
    for _, user := range users {
        if user.ID == id {
            response := Response{
                Success: true,
                Message: "User found",
                Data:    user,
            }
            json.NewEncoder(w).Encode(response)
            return
        }
    }
    
    // User not found
    response := Response{
        Success: false,
        Message: "User not found",
    }
    w.WriteHeader(http.StatusNotFound)
    json.NewEncoder(w).Encode(response)
}

// Handler for creating a new user
func createUserHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    
    var newUser User
    if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
        response := Response{
            Success: false,
            Message: "Invalid JSON",
        }
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(response)
        return
    }
    
    // Generate new ID
    newUser.ID = len(users) + 1
    newUser.Created = time.Now().Format(time.RFC3339)
    
    users = append(users, newUser)
    
    response := Response{
        Success: true,
        Message: "User created successfully",
        Data:    newUser,
    }
    
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(response)
}

func main() {
    // Define routes
    http.HandleFunc("/", loggingMiddleware(homeHandler))
    http.HandleFunc("/api/users", loggingMiddleware(getUsersHandler))
    http.HandleFunc("/api/users/", loggingMiddleware(getUserHandler))
    http.HandleFunc("/api/users/create", loggingMiddleware(createUserHandler))
    
    // Start server
    fmt.Println("Server starting on http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Code Editor

Output