Featured image of post

Golang ebook intro how to build a web app with golang.

Build web application with Golang

The “Build web application with Golang” library is a comprehensive ebook and learning resource that teaches developers how to build web applications using the Go programming language. With an impressive 44,185 stars on GitHub, this resource has become one of the most popular guides for Go web development. Created by Asta Xie, this book provides a thorough introduction to web development concepts, HTTP servers, routing, middleware, database integration, and modern web application architecture patterns using Go.

Developers should care about this library because it bridges the gap between learning Go syntax and building production-ready web applications. The resource covers everything from basic HTTP handling to advanced topics like WebSocket communication, RESTful API design, and deployment strategies. Whether you’re a beginner learning Go or an experienced developer transitioning from other languages, this guide provides practical examples and best practices that are immediately applicable to real-world projects.

Real-world use cases include building REST APIs for microservices, creating full-stack web applications, implementing WebSocket-based real-time features, and developing high-performance web servers that can handle thousands of concurrent connections. The problems it solves include understanding Go’s net/http package, implementing proper request routing, handling authentication and authorization, integrating with databases, and structuring web applications for maintainability and scalability.

Key Features

The library covers several key features that make Go an excellent choice for web development:

Comprehensive HTTP Server Implementation - The guide thoroughly explains how to use Go’s net/http package to create robust HTTP servers. It covers request handling, response writing, middleware implementation, and server configuration. The resource shows how to handle different HTTP methods, manage request headers, and implement proper error handling for production environments.

Advanced Routing Systems - Beyond basic HTTP handling, the library demonstrates how to implement sophisticated routing systems using third-party routers like Gorilla Mux or Chi. These routers provide features like path parameters, query string parsing, route groups, and middleware chaining that are essential for building complex web applications.

Database Integration Patterns - The guide covers various database integration approaches, including SQL databases using database/sql, NoSQL databases like MongoDB, and Redis for caching. It explains connection pooling, transaction management, and ORM usage patterns that are crucial for data persistence in web applications.

WebSocket Communication - Modern web applications often require real-time communication, and the library provides detailed examples of implementing WebSocket servers in Go. It covers connection management, message broadcasting, and handling different WebSocket events, enabling developers to build chat applications, live dashboards, and collaborative tools.

Authentication and Authorization - Security is a critical aspect of web development, and the resource covers various authentication methods including JWT tokens, OAuth integration, and session management. It explains how to implement middleware for protecting routes, handling user authentication flows, and securing API endpoints.

Template Rendering and Static File Serving - The library demonstrates how to use Go’s html/template package for server-side rendering, including template inheritance, custom functions, and security best practices. It also covers serving static files efficiently and implementing content delivery strategies.

Installation and Setup

The “Build web application with Golang” library is primarily a learning resource and ebook, so there’s no traditional installation process. However, to follow along with the examples and build the applications described in the guide, you’ll need to set up a Go development environment:

 1# Ensure you have Go installed (version 1.16 or higher recommended)
 2go version
 3
 4# Clone the repository to access all examples and source code
 5git clone https://github.com/astaxie/build-web-application-with-golang.git
 6cd build-web-application-with-golang
 7
 8# Navigate to the appropriate chapter directory for examples
 9# For instance, to access chapter 4 examples:
10cd examples/chapter4

The resource requires Go 1.16 or higher for most examples, though some specific features might require newer versions. No additional dependencies are required for the basic examples, but some advanced chapters may require third-party libraries like Gorilla Mux for routing or specific database drivers.

To verify your setup, you can run one of the basic examples:

1cd examples/chapter4/1
2go run main.go

This should start a simple HTTP server that you can access at http://localhost:8080, confirming that your Go environment is properly configured to run the examples.

Basic Usage

Here’s a minimal “Hello World” example that demonstrates the basic structure of a Go web application:

 1package main
 2
 3import (
 4	"fmt"
 5	"net/http"
 6)
 7
 8func sayHello(w http.ResponseWriter, r *http.Request) {
 9	fmt.Fprintf(w, "Hello, World!")
10}
11
12func main() {
13	http.HandleFunc("/", sayHello)
14	
15	fmt.Println("Server starting on port 8080...")
16	err := http.ListenAndServe(":8080", nil)
17	if err != nil {
18		fmt.Printf("Server failed to start: %s\n", err)
19	}
20}

This example demonstrates the fundamental pattern used throughout Go web development. The sayHello function is a handler that takes an http.ResponseWriter and *http.Request as parameters. It uses fmt.Fprintf to write a simple “Hello, World!” response to the client.

In the main function, http.HandleFunc registers the handler function for the root path ("/"). The http.ListenAndServe function starts the HTTP server on port 8080. The second parameter is nil, which means it uses the default ServeMux (multiplexer) that routes incoming requests to the appropriate handlers.

When you run this program with go run main.go, it will start an HTTP server that listens on port 8080. You can then open your browser and navigate to http://localhost:8080 to see the “Hello, World!” message. The server will continue running until you stop it (usually with Ctrl+C).

The expected output in your terminal will be:

1Server starting on port 8080...

And visiting http://localhost:8080 in your browser will display:

1Hello, World!

Real-World Examples

Example 1: REST API Server with Middleware and Database Integration

  1package main
  2
  3import (
  4	"context"
  5	"database/sql"
  6	"encoding/json"
  7	"fmt"
  8	"log"
  9	"net/http"
 10	"os"
 11	"time"
 12
 13	_ "github.com/lib/pq"
 14	"github.com/gorilla/mux"
 15)
 16
 17// User represents a user in the system
 18type User struct {
 19	ID        string    `json:"id"`
 20	Name      string    `json:"name"`
 21	Email     string    `json:"email"`
 22	CreatedAt time.Time `json:"created_at"`
 23}
 24
 25// Database connection pool
 26var db *sql.DB
 27
 28// Connect to PostgreSQL database
 29func connectDB() error {
 30	var err error
 31	connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
 32		os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"),
 33		os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))
 34	
 35	db, err = sql.Open("postgres", connStr)
 36	if err != nil {
 37		return err
 38	}
 39
 40	// Test the connection
 41	err = db.Ping()
 42	if err != nil {
 43		return err
 44	}
 45
 46	// Configure connection pool
 47	db.SetMaxOpenConns(25)
 48	db.SetMaxIdleConns(25)
 49	db.SetConnMaxLifetime(5 * time.Minute)
 50
 51	log.Println("Successfully connected to database")
 52	return nil
 53}
 54
 55// Middleware for logging requests
 56func loggingMiddleware(next http.Handler) http.Handler {
 57	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 58		log.Printf("%s %s %s", r.Method, r.RequestURI, r.RemoteAddr)
 59		next.ServeHTTP(w, r)
 60	})
 61}
 62
 63// Middleware for CORS
 64func corsMiddleware(next http.Handler) http.Handler {
 65	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 66		w.Header().Set("Access-Control-Allow-Origin", "*")
 67		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
 68		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
 69		
 70		if r.Method == "OPTIONS" {
 71			w.WriteHeader(http.StatusOK)
 72			return
 73		}
 74		
 75		next.ServeHTTP(w, r)
 76	})
 77}
 78
 79// Get all users
 80func getUsers(w http.ResponseWriter, r *http.Request) {
 81	ctx := context.Background()
 82	rows, err := db.QueryContext(ctx, "SELECT id, name, email, created_at FROM users")
 83	if err != nil {
 84		http.Error(w, err.Error(), http.StatusInternalServerError)
 85		return
 86	}
 87	defer rows.Close()
 88
 89	var users []User
 90	for rows.Next() {
 91		var user User
 92		err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.CreatedAt)
 93		if err != nil {
 94			http.Error(w, err.Error(), http.StatusInternalServerError)
 95			return
 96		}
 97		users = append(users, user)
 98	}
 99
100	w.Header().Set("Content-Type", "application/json")
101	json.NewEncoder(w).Encode(users)
102}
103
104// Create a new user
105func createUser(w http.ResponseWriter, r *http.Request) {
106	var user User
107	err := json.NewDecoder(r.Body).Decode(&user)
108	if err != nil {
109		http.Error(w, err.Error(), http.StatusBadRequest)
110		return
111	}
112
113	ctx := context.Background()
114	err = db.QueryRowContext(ctx, "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, created_at",
115		user.Name, user.Email).Scan(&user.ID, &user.CreatedAt)
116	if err != nil {
117		http.Error(w, err.Error(), http.StatusInternalServerError)
118		return
119	}
120
121	w.Header().Set("Content-Type", "application/json")
122	w.WriteHeader(http.StatusCreated)
123	json.NewEncoder(w).Encode(user)
124}
125
126func main() {
127	// Connect to database
128	err := connectDB()
129	if err != nil {
130		log.Fatalf("Failed to connect to database: %s", err)
131	}
132	defer db.Close()
133
134	// Setup router
135	r := mux.NewRouter()
136	r.Use(loggingMiddleware)
137	r.Use(corsMiddleware)
138
139	// API routes
140	r.HandleFunc("/api/users", getUsers).Methods("GET")
141	r.HandleFunc("/api/users", createUser).Methods("POST")
142
143	// Start server
144	port := ":8080"
145	log.Printf("Server starting on port %s...\n", port)
146	err = http.ListenAndServe(port, r)
147	if err != nil {
148		log.Fatalf("Server failed to start: %s", err)
149	}
150}

This example demonstrates a complete REST API server with database integration, middleware, and proper error handling. The server uses PostgreSQL as the database and implements CRUD operations for user management.

The code includes connection pooling configuration for optimal database performance, logging middleware to track all incoming requests, and CORS middleware to handle cross-origin requests. The API provides endpoints for retrieving all users and creating new users, with proper JSON encoding/decoding and HTTP status codes.

Expected output when running the server:

12024/01/01 12:00:00 Successfully connected to database
22024/01/01 12:00:00 Server starting on port :8080...

When making requests to the API:

  • GET /api/users returns a JSON array of all users
  • POST /api/users with JSON body creates a new user and returns the created user with ID and timestamp

Example 2: WebSocket Chat Application

  1package main
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"log"
  7	"net/http"
  8	"sync"
  9	"time"
 10
 11	"github.com/gorilla/websocket"
 12)
 13
 14// Message represents a chat message
 15type Message struct {
 16	Username string `json:"username"`
 17	Text     string `json:"text"`
 18	Timestamp int64 `json:"timestamp"`
 19}
 20
 21// Client represents a connected WebSocket client
 22type Client struct {
 23	conn *websocket.Conn
 24	send chan Message
 25	name string
 26}
 27
 28// Hub maintains the set of active clients and broadcasts messages
 29type Hub struct {
 30	clients    map[*Client]bool
 31	broadcast  chan Message
 32	register   chan *Client
 33	unregister chan *Client
 34	mu         sync.RWMutex
 35}
 36
 37var upgrader = websocket.Upgrader{
 38	ReadBufferSize:  1024,
 39	WriteBufferSize: 1024,
 40	CheckOrigin: func(r *http.Request) bool {
 41		return true
 42	},
 43}
 44
 45func newHub() *Hub {
 46	return &Hub{
 47		clients:    make(map[*Client]bool),
 48		broadcast:  make(chan Message),
 49		register:   make(chan *Client),
 50		unregister: make(chan *Client),
 51	}
 52}
 53
 54func (h *Hub) run() {
 55	for {
 56		select {
 57		case client := <-h.register:
 58			h.mu.Lock()
 59			h.clients[client] = true
 60			h.mu.Unlock()
 61			log.Printf("Client connected: %s", client.name)
 62		case client := <-h.unregister:
 63			h.mu.Lock()
 64			if _, exists := h.clients[client]; exists {
 65				delete(h.clients, client)
 66				close(client.send)
 67				log.Printf("Client disconnected: %s", client.name)
 68			}
 69			h.mu.Unlock()
 70		case message := <-h.broadcast:
 71			h.mu.RLock()
 72			for client := range h.clients {
 73				select {
 74				case client.send <- message:
 75				default:
 76					close(client.send)
 77					delete(h.clients, client)
 78				}
 79			}
 80			h.mu.RUnlock()
 81		}
 82	}
 83}
 84
 85func (c *Client) readPump() {
 86	defer func() {
 87		h.unregister <- c
 88		c.conn.Close()
 89	}()
 90	c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
 91	c.conn.SetPongHandler(func(string) error {
 92		c.conn.SetReadDeadline(time.Now().Add(60 * time.Second))
 93		return nil
 94	})
 95	
 96	for {
 97		_, message, err := c.conn.ReadMessage()
 98		if err != nil {
 99			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
100				log.Printf("Error reading message: %v", err)
101			}
102			break
103		}
104		
105		var msg Message
106		err = json.Unmarshal(message, &msg)
107		if err != nil {
108			log.Printf("Invalid message format: %v", err)
109			continue
110		}
111		msg.Timestamp = time.Now().Unix()
112		h.broadcast <- msg
113	}
114}
115
116func (c *Client) writePump() {
117	ticker := time.NewTicker(30 * time.Second)
118	defer func() {
119		ticker.Stop()
120		c.conn.Close()
121	}()
122	
123	for {
124		select {
125		case message, ok := <-c.send:
126			c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
127			if !ok {
128				c.conn.WriteMessage(websocket.CloseMessage, []byte{})
129				return
130			}
131			
132			w, err := c.conn.NextWriter(websocket.TextMessage)
133			if err != nil {
134				return
135			}
136			jsonMessage, _ := json.Marshal(message)
137			w.Write(jsonMessage)
138			
139			n := len(c.send)
140			for i := 0; i < n; i++ {
141				jsonMessage, _ := json.Marshal(<-c.send)
142				w.Write(jsonMessage)
143			}
144			
145			if err := w.Close(); err != nil {
146				return
147			}
148		case <-ticker.C:
149			c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
150			if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
151				return
152			}
153		}
154	}
155}
156
157var h = newHub()
158
159func serveWs(w http.ResponseWriter, r *http.Request) {
160	conn, err := upgrader.Upgrade(w, r, nil)
161	if err != nil {
162		log.Println(err)
163		return
164	}
165	
166	username := r.URL.Query().Get("username")
167	if username == "" {
168		username = "Anonymous"
169	}
170	
171	client := &Client{
172		conn: conn,
173		send: make(chan Message, 256),
174		name: username,
175	}
176	
177	h.register <- client
178	go client.writePump()
179	go client.readPump()
180}
181
182func main() {
183	go h.run()
184	
185	http.HandleFunc("/ws", serveWs)
186	http.Handle("/", http.FileServer(http.Dir("./static")))
187	
188	log.Println("WebSocket server starting on :8080...")
189	err := http.ListenAndServe(":8080", nil)
190	if err != nil {
191		log.Fatal("ListenAndServe: ", err)
192	}
193}

This WebSocket chat application demonstrates real-time communication capabilities in Go. The server maintains a hub that manages connected clients and broadcasts messages to all participants.

The implementation includes proper connection management with read/write pumps for each client, heartbeat mechanisms using ping/pong messages to detect dead connections, and concurrent-safe client registration/deregistration using mutexes. The hub uses channels to handle client registration, unregistration, and message broadcasting in a non-blocking manner.

The WebSocket upgrader is configured to accept connections from any origin (useful for development), and the server serves static files from a “static” directory for the chat interface. Clients connect using the /ws endpoint with an optional username parameter.

Expected behavior:

  • Multiple clients can connect simultaneously
  • Messages sent by any client are broadcast to all connected clients
  • Connection timeouts are handled gracefully
  • The server maintains a list of active clients and cleans up disconnected ones

Best Practices and Common Pitfalls

Always handle errors properly - Go’s explicit error handling can seem verbose, but it’s crucial for building robust web applications. Never ignore errors, especially when dealing with database operations, file I/O, or external API calls. Use proper HTTP status codes and meaningful error messages for API responses.

Use context for request-scoped values - The context package is essential for managing request deadlines, cancellation signals, and passing request-scoped values through your call chain. Always pass context.Context as the first parameter in functions that might block or need cancellation support.

Implement proper middleware chaining - When building middleware, ensure they call the next handler in the chain. Use third-party routers like Gorilla Mux or Chi for more sophisticated routing needs, as they provide better middleware support and route grouping capabilities than the default ServeMux.

Secure your applications - Always validate and sanitize user inputs to prevent injection attacks. Use parameterized queries for database operations, implement proper authentication and authorization, and consider using HTTPS in production. Set appropriate security headers and implement rate limiting to prevent abuse.

Avoid common pitfalls - Don’t use global variables for request-scoped data, as this can lead to race conditions in concurrent environments. Be careful with closures in goroutines that capture loop variables. Always close resources like database connections, files, and response bodies to prevent resource leaks.

Monitor and log effectively - Implement structured logging with proper log levels. Use metrics and monitoring to track application performance and health. Consider using observability tools like OpenTelemetry for distributed tracing in complex applications.

Conclusion

The “Build web application with Golang” resource is an invaluable guide for anyone looking to master web development with Go. With its comprehensive coverage of HTTP servers, routing, database integration, WebSocket communication, and security best practices, it provides everything needed to build production-ready web applications. The 44,185 stars on GitHub demonstrate its popularity and effectiveness as a learning resource.

Whether you’re building REST APIs, real-time chat applications, or full-stack web applications, the concepts and patterns covered in this guide will help you write clean, efficient, and maintainable Go code. The practical examples and real-world scenarios make it easy to apply the knowledge directly to your projects.

For more information and to access all the examples and source code, visit the GitHub repository: https://github.com/astaxie/build-web-application-with-golang

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy