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.
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/2Framework Ecosystem
Rich ecosystem of web frameworks offering different trade-offs between performance, features, and ease of use for various application needs.
Gin Echo FiberPerformance & 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 ScalableBasic 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}}{{end}}{{end}}{{.Name}}
Price: ${{.Price}}
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
Echo
Best for: Feature-rich applications
- Comprehensive feature set
- Built-in middleware library
- WebSocket support
- Template rendering
- Auto-generated OpenAPI
Fiber
Best for: Express.js developers
- Express.js-like API
- Fast HTTP engine (fasthttp)
- Built-in middleware
- WebSocket support
- Easy migration from Node.js
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 REST2. Real-time Chat Application
Create a WebSocket-based chat app with rooms, user management, message history, and real-time notifications.
Intermediate WebSocket3. Microservices Architecture
Design a microservices system with service discovery, load balancing, distributed tracing, and fault tolerance.
Advanced Microservices4. High-Performance Web Server
Build a web server handling 100k+ concurrent connections with minimal latency and memory usage.
Expert High-Performance