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}}
{{.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
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