Go Web Development and REST APIs
60 minGo's `net/http` package provides comprehensive HTTP server and client functionality, enabling you to build robust web services. The package handles HTTP requests, responses, routing, and middleware. Go's standard library is powerful enough for many production applications. Understanding `net/http` is essential for building web services in Go.
REST APIs follow standard HTTP methods (GET, POST, PUT, DELETE) and status codes (200, 201, 400, 404, 500), providing a consistent interface for clients. REST principles include resource-based URLs, stateless communication, and standard HTTP semantics. Understanding REST helps you design clean, maintainable APIs.
Middleware functions process requests before reaching handlers, enabling cross-cutting concerns like logging, authentication, CORS, and request validation. Middleware wraps handlers, allowing you to execute code before and after handler execution. Understanding middleware helps you build maintainable, secure web applications.
JSON encoding/decoding handles data serialization, enabling APIs to exchange structured data. Go's `encoding/json` package provides efficient JSON encoding and decoding. Struct tags control JSON field names and behavior. Understanding JSON handling enables you to build modern REST APIs.
Best practices include using proper HTTP methods and status codes, implementing consistent error handling, validating input data, using middleware for cross-cutting concerns, and structuring code clearly. APIs should be secure, performant, and well-documented. Understanding REST API development enables you to build production-ready services.
Advanced topics include request validation, authentication and authorization, rate limiting, API versioning, and documentation. Production APIs require careful design and implementation. Understanding these topics helps you build enterprise-grade APIs.
Key Concepts
- net/http provides HTTP server and client functionality.
- REST APIs follow standard HTTP methods and status codes.
- Middleware processes requests before handlers.
- JSON encoding/decoding handles data serialization.
- REST principles enable clean, maintainable APIs.
Learning Objectives
Master
- Building REST APIs with Go's net/http package
- Implementing proper HTTP methods and status codes
- Creating and using middleware for cross-cutting concerns
- Handling JSON encoding and decoding
Develop
- REST API design thinking
- Understanding HTTP and web service principles
- Designing secure, maintainable APIs
Tips
- Use proper HTTP methods and status codes consistently.
- Implement middleware for logging, authentication, and CORS.
- Validate input data before processing.
- Return consistent error responses.
Common Pitfalls
- Not using proper HTTP methods, causing confusion.
- Not handling errors properly, returning unclear error messages.
- Not validating input, causing security vulnerabilities.
- Creating handlers that are too large or do too much.
Summary
- Go's net/http package enables building robust web services.
- REST APIs follow standard HTTP methods and status codes.
- Middleware enables cross-cutting concerns.
- JSON encoding/decoding enables structured data exchange.
- Understanding REST API development enables production-ready services.
Exercise
Build a complete REST API with CRUD operations, middleware, and proper error handling.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
)
// User represents a user in the system
type User struct {
ID int "json:"id""
Name string "json:"name""
Email string "json:"email""
CreatedAt time.Time "json:"created_at""
UpdatedAt time.Time "json:"updated_at""
}
// UserStore manages user data (in-memory for demo)
type UserStore struct {
users map[int]*User
nextID int
mu sync.RWMutex
}
// NewUserStore creates a new user store
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]*User),
nextID: 1,
}
}
// CreateUser adds a new user
func (s *UserStore) CreateUser(name, email string) *User {
s.mu.Lock()
defer s.mu.Unlock()
user := &User{
ID: s.nextID,
Name: name,
Email: email,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
s.users[user.ID] = user
s.nextID++
return user
}
// GetUser retrieves a user by ID
func (s *UserStore) GetUser(id int) (*User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
user, exists := s.users[id]
return user, exists
}
// UpdateUser updates an existing user
func (s *UserStore) UpdateUser(id int, name, email string) (*User, bool) {
s.mu.Lock()
defer s.mu.Unlock()
user, exists := s.users[id]
if !exists {
return nil, false
}
user.Name = name
user.Email = email
user.UpdatedAt = time.Now()
return user, true
}
// DeleteUser removes a user
func (s *UserStore) DeleteUser(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, exists := s.users[id]
if !exists {
return false
}
delete(s.users, id)
return true
}
// GetAllUsers returns all users
func (s *UserStore) GetAllUsers() []*User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]*User, 0, len(s.users))
for _, user := range s.users {
users = append(users, user)
}
return users
}
// API handlers
type APIHandler struct {
store *UserStore
}
func (h *APIHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string "json:"name""
Email string "json:"email""
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
if req.Name == "" || req.Email == "" {
http.Error(w, "Name and email are required", http.StatusBadRequest)
return
}
user := h.store.CreateUser(req.Name, req.Email)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func (h *APIHandler) GetUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
user, exists := h.store.GetUser(id)
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func (h *APIHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
var req struct {
Name string "json:"name""
Email string "json:"email""
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
user, exists := h.store.UpdateUser(id, req.Name, req.Email)
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func (h *APIHandler) DeleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
if !h.store.DeleteUser(id) {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *APIHandler) GetAllUsers(w http.ResponseWriter, r *http.Request) {
users := h.store.GetAllUsers()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
// Middleware
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Call next handler
next.ServeHTTP(w, r)
// Log request details
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(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")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
store := NewUserStore()
handler := &APIHandler{store: store}
// Create router
r := mux.NewRouter()
// Apply middleware
r.Use(loggingMiddleware)
r.Use(corsMiddleware)
// Define routes
r.HandleFunc("/api/users", handler.CreateUser).Methods("POST")
r.HandleFunc("/api/users", handler.GetAllUsers).Methods("GET")
r.HandleFunc("/api/users/{id}", handler.GetUser).Methods("GET")
r.HandleFunc("/api/users/{id}", handler.UpdateUser).Methods("PUT")
r.HandleFunc("/api/users/{id}", handler.DeleteUser).Methods("DELETE")
// Health check
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}).Methods("GET")
log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", r))
}