Skip to main content

Go REST API Development

REST API development in Go involves creating web services that follow REST (Representational State Transfer) principles. Go's standard library provides excellent support for building RESTful APIs with proper HTTP methods, status codes, and JSON handling. Understanding REST API development is essential for creating modern web services and microservices. This comprehensive guide will teach you everything you need to know about building REST APIs in Go.

Understanding REST APIs

What Is a REST API?

A REST API (Representational State Transfer Application Programming Interface) is an architectural style for designing networked applications. It provides:

  • Resource-based URLs - URLs represent resources, not actions
  • HTTP Methods - Using HTTP methods to indicate actions
  • Stateless Communication - Each request contains all necessary information
  • JSON/XML Responses - Structured data exchange formats
  • Standard Status Codes - HTTP status codes for response status

REST API Principles

Resource Identification

Resources are identified by URLs and manipulated using HTTP methods.

HTTP Methods

Different HTTP methods represent different operations on resources.

Stateless Communication

Each request is independent and contains all necessary information.

Basic REST API Structure

Resource-based URL Design

RESTful URL Patterns

Designing URLs that represent resources and operations.

HTTP Method Mapping

Mapping HTTP methods to CRUD operations.

package main

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

func main() {
// Basic REST API structure examples
fmt.Println("Basic REST API structure examples:")

// User resource API
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Created time.Time `json:"created"`
}

// In-memory storage for demo
users := make(map[int]User)
nextID := 1

// Create user
func createUser(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

newUser.ID = nextID
newUser.Created = time.Now()
users[nextID] = newUser
nextID++

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}

// Get all users
func getAllUsers(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

userList := make([]User, 0, len(users))
for _, user := range users {
userList = append(userList, user)
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userList)
}

// Get user by ID
func getUserByID(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/users/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}

user, exists := users[id]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}

// Update user
func updateUser(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/users/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}

user, exists := users[id]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}

var updatedUser User
err = json.NewDecoder(r.Body).Decode(&updatedUser)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

updatedUser.ID = user.ID
updatedUser.Created = user.Created
users[id] = updatedUser

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(updatedUser)
}

// Delete user
func deleteUser(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/users/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}

_, exists := users[id]
if !exists {
http.Error(w, "User not found", http.StatusNotFound)
return
}

delete(users, id)
w.WriteHeader(http.StatusNoContent)
}

// Basic REST API server
func basicRESTAPIServer() {
mux := http.NewServeMux()

// User routes
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getAllUsers(w, r)
case "POST":
createUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})

mux.HandleFunc("/api/users/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getUserByID(w, r)
case "PUT":
updateUser(w, r)
case "DELETE":
deleteUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})

fmt.Println("Basic REST API server starting on :8080")
http.ListenAndServe(":8080", mux)
}

// Start basic REST API server
go basicRESTAPIServer()
time.Sleep(2 * time.Second)

fmt.Println("Basic REST API structure examples completed!")
}

HTTP Methods and Status Codes

HTTP Method Usage

Understanding when and how to use different HTTP methods.

Status Code Implementation

Implementing proper HTTP status codes for different scenarios.

package main

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

func main() {
// HTTP methods and status codes examples
fmt.Println("HTTP methods and status codes examples:")

// Product API with proper status codes
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
Stock int `json:"stock"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}

products := make(map[int]Product)
nextProductID := 1

// Create product with validation
func createProduct(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var newProduct Product
err := json.NewDecoder(r.Body).Decode(&newProduct)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

// Validation
if newProduct.Name == "" {
http.Error(w, "Product name is required", http.StatusBadRequest)
return
}

if newProduct.Price < 0 {
http.Error(w, "Product price must be positive", http.StatusBadRequest)
return
}

newProduct.ID = nextProductID
newProduct.Created = time.Now()
newProduct.Updated = time.Now()
products[nextProductID] = newProduct
nextProductID++

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newProduct)
}

// Get products with pagination
func getProducts(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

// Parse query parameters
page := 1
limit := 10

if p := r.URL.Query().Get("page"); p != "" {
if parsedPage, err := strconv.Atoi(p); err == nil && parsedPage > 0 {
page = parsedPage
}
}

if l := r.URL.Query().Get("limit"); l != "" {
if parsedLimit, err := strconv.Atoi(l); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
limit = parsedLimit
}
}

// Calculate pagination
total := len(products)
start := (page - 1) * limit
end := start + limit

if start >= total {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode([]Product{})
return
}

if end > total {
end = total
}

// Get products for current page
productList := make([]Product, 0, limit)
count := 0
for _, product := range products {
if count >= start && count < end {
productList = append(productList, product)
}
count++
}

// Response with pagination info
response := map[string]interface{}{
"data": productList,
"pagination": map[string]interface{}{
"page": page,
"limit": limit,
"total": total,
"totalPages": (total + limit - 1) / limit,
},
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

// Get product by ID
func getProductByID(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/products/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid product ID", http.StatusBadRequest)
return
}

product, exists := products[id]
if !exists {
http.Error(w, "Product not found", http.StatusNotFound)
return
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(product)
}

// Update product (partial update)
func updateProduct(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/products/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid product ID", http.StatusBadRequest)
return
}

product, exists := products[id]
if !exists {
http.Error(w, "Product not found", http.StatusNotFound)
return
}

var updateData map[string]interface{}
err = json.NewDecoder(r.Body).Decode(&updateData)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

// Update fields
if name, ok := updateData["name"].(string); ok && name != "" {
product.Name = name
}

if description, ok := updateData["description"].(string); ok {
product.Description = description
}

if price, ok := updateData["price"].(float64); ok && price >= 0 {
product.Price = price
}

if stock, ok := updateData["stock"].(float64); ok && stock >= 0 {
product.Stock = int(stock)
}

product.Updated = time.Now()
products[id] = product

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(product)
}

// Delete product
func deleteProduct(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

path := strings.TrimPrefix(r.URL.Path, "/api/products/")
id, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "Invalid product ID", http.StatusBadRequest)
return
}

_, exists := products[id]
if !exists {
http.Error(w, "Product not found", http.StatusNotFound)
return
}

delete(products, id)
w.WriteHeader(http.StatusNoContent)
}

// HTTP methods and status codes server
func httpMethodsStatusCodesServer() {
mux := http.NewServeMux()

// Product routes
mux.HandleFunc("/api/products", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getProducts(w, r)
case "POST":
createProduct(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})

mux.HandleFunc("/api/products/", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getProductByID(w, r)
case "PUT":
updateProduct(w, r)
case "DELETE":
deleteProduct(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})

fmt.Println("HTTP methods and status codes server starting on :8081")
http.ListenAndServe(":8081", mux)
}

// Start HTTP methods and status codes server
go httpMethodsStatusCodesServer()
time.Sleep(2 * time.Second)

fmt.Println("HTTP methods and status codes examples completed!")
}

JSON Handling and Validation

JSON Request/Response Handling

JSON Encoding and Decoding

Working with JSON data in REST APIs.

Input Validation

Validating JSON input data.

package main

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

func main() {
// JSON handling and validation examples
fmt.Println("JSON handling and validation examples:")

// User with validation
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
Phone string `json:"phone"`
Password string `json:"password,omitempty"`
}

// Validation errors
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}

type ValidationResponse struct {
Errors []ValidationError `json:"errors"`
}

users := make(map[int]User)
nextUserID := 1

// Validation functions
func validateEmail(email string) bool {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
return emailRegex.MatchString(email)
}

func validatePhone(phone string) bool {
phoneRegex := regexp.MustCompile(`^\+?[\d\s\-\(\)]{10,}$`)
return phoneRegex.MatchString(phone)
}

func validateUser(user User) []ValidationError {
var errors []ValidationError

if strings.TrimSpace(user.Name) == "" {
errors = append(errors, ValidationError{
Field: "name",
Message: "Name is required",
})
} else if len(user.Name) < 2 {
errors = append(errors, ValidationError{
Field: "name",
Message: "Name must be at least 2 characters",
})
}

if strings.TrimSpace(user.Email) == "" {
errors = append(errors, ValidationError{
Field: "email",
Message: "Email is required",
})
} else if !validateEmail(user.Email) {
errors = append(errors, ValidationError{
Field: "email",
Message: "Email format is invalid",
})
}

if user.Age < 0 || user.Age > 150 {
errors = append(errors, ValidationError{
Field: "age",
Message: "Age must be between 0 and 150",
})
}

if user.Phone != "" && !validatePhone(user.Phone) {
errors = append(errors, ValidationError{
Field: "phone",
Message: "Phone format is invalid",
})
}

return errors
}

// Create user with validation
func createUserWithValidation(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

var newUser User
err := json.NewDecoder(r.Body).Decode(&newUser)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

// Validate user
validationErrors := validateUser(newUser)
if len(validationErrors) > 0 {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(ValidationResponse{Errors: validationErrors})
return
}

newUser.ID = nextUserID
newUser.Password = "" // Remove password from response
users[nextUserID] = newUser
nextUserID++

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}

// Get users with JSON response
func getUsersWithJSON(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

userList := make([]User, 0, len(users))
for _, user := range users {
user.Password = "" // Remove password from response
userList = append(userList, user)
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(userList)
}

// Search users with JSON query
func searchUsersWithJSON(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

type SearchQuery struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}

var query SearchQuery
err := json.NewDecoder(r.Body).Decode(&query)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}

var results []User
for _, user := range users {
match := true

if query.Name != "" && !strings.Contains(strings.ToLower(user.Name), strings.ToLower(query.Name)) {
match = false
}

if query.Email != "" && !strings.Contains(strings.ToLower(user.Email), strings.ToLower(query.Email)) {
match = false
}

if query.Age > 0 && user.Age != query.Age {
match = false
}

if match {
user.Password = "" // Remove password from response
results = append(results, user)
}
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}

// JSON handling and validation server
func jsonHandlingValidationServer() {
mux := http.NewServeMux()

mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getUsersWithJSON(w, r)
case "POST":
createUserWithValidation(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})

mux.HandleFunc("/api/users/search", searchUsersWithJSON)

fmt.Println("JSON handling and validation server starting on :8082")
http.ListenAndServe(":8082", mux)
}

// Start JSON handling and validation server
go jsonHandlingValidationServer()
time.Sleep(2 * time.Second)

fmt.Println("JSON handling and validation examples completed!")
}

API Versioning and Documentation

API Versioning Strategies

URL Versioning

Implementing API versioning in URLs.

Header Versioning

Using headers for API versioning.

package main

import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)

func main() {
// API versioning and documentation examples
fmt.Println("API versioning and documentation examples:")

// API versioning with URL
func apiVersioningWithURL() {
mux := http.NewServeMux()

// API v1
mux.HandleFunc("/api/v1/users", func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"version": "v1",
"message": "Users API v1",
"features": []string{"basic_crud", "simple_search"},
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})

// API v2
mux.HandleFunc("/api/v2/users", func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"version": "v2",
"message": "Users API v2",
"features": []string{"advanced_crud", "complex_search", "pagination", "filtering"},
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})

fmt.Println("API versioning with URL server starting on :8083")
http.ListenAndServe(":8083", mux)
}

// Start API versioning with URL server
go apiVersioningWithURL()
time.Sleep(2 * time.Second)

// API versioning with headers
func apiVersioningWithHeaders() {
mux := http.NewServeMux()

mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
version := r.Header.Get("API-Version")
if version == "" {
version = "v1" // Default version
}

var response map[string]interface{}

switch version {
case "v1":
response = map[string]interface{}{
"version": "v1",
"message": "Users API v1 (header versioning)",
"features": []string{"basic_crud", "simple_search"},
}
case "v2":
response = map[string]interface{}{
"version": "v2",
"message": "Users API v2 (header versioning)",
"features": []string{"advanced_crud", "complex_search", "pagination", "filtering"},
}
default:
http.Error(w, "Unsupported API version", http.StatusBadRequest)
return
}

w.Header().Set("Content-Type", "application/json")
w.Header().Set("API-Version", version)
json.NewEncoder(w).Encode(response)
})

fmt.Println("API versioning with headers server starting on :8084")
http.ListenAndServe(":8084", mux)
}

// Start API versioning with headers server
go apiVersioningWithHeaders()
time.Sleep(2 * time.Second)

// API documentation endpoint
func apiDocumentationEndpoint() {
mux := http.NewServeMux()

mux.HandleFunc("/api/docs", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}

documentation := map[string]interface{}{
"title": "Users API Documentation",
"version": "1.0.0",
"description": "RESTful API for managing users",
"endpoints": []map[string]interface{}{
{
"path": "/api/users",
"method": "GET",
"description": "Get all users",
"parameters": []map[string]string{
{"name": "page", "type": "integer", "description": "Page number"},
{"name": "limit", "type": "integer", "description": "Items per page"},
},
},
{
"path": "/api/users",
"method": "POST",
"description": "Create a new user",
"body": map[string]interface{}{
"name": "string (required)",
"email": "string (required)",
"age": "integer (optional)",
"phone": "string (optional)",
},
},
{
"path": "/api/users/{id}",
"method": "GET",
"description": "Get user by ID",
"parameters": []map[string]string{
{"name": "id", "type": "integer", "description": "User ID"},
},
},
{
"path": "/api/users/{id}",
"method": "PUT",
"description": "Update user by ID",
"parameters": []map[string]string{
{"name": "id", "type": "integer", "description": "User ID"},
},
"body": map[string]interface{}{
"name": "string (optional)",
"email": "string (optional)",
"age": "integer (optional)",
"phone": "string (optional)",
},
},
{
"path": "/api/users/{id}",
"method": "DELETE",
"description": "Delete user by ID",
"parameters": []map[string]string{
{"name": "id", "type": "integer", "description": "User ID"},
},
},
},
"status_codes": []map[string]interface{}{
{"code": 200, "description": "Success"},
{"code": 201, "description": "Created"},
{"code": 400, "description": "Bad Request"},
{"code": 404, "description": "Not Found"},
{"code": 500, "description": "Internal Server Error"},
},
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(documentation)
})

fmt.Println("API documentation endpoint server starting on :8085")
http.ListenAndServe(":8085", mux)
}

// Start API documentation endpoint server
go apiDocumentationEndpoint()
time.Sleep(2 * time.Second)

fmt.Println("API versioning and documentation examples completed!")
}

Error Handling and Logging

Comprehensive Error Handling

Error Response Format

Standardizing error responses across the API.

Error Logging

Implementing comprehensive error logging.

package main

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

func main() {
// Error handling and logging examples
fmt.Println("Error handling and logging examples:")

// Error response structure
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
Time string `json:"time"`
}

// Logging middleware
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()

// Log request
log.Printf("Request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr)

next.ServeHTTP(w, r)

// Log response
log.Printf("Response: %s %s in %v", r.Method, r.URL.Path, time.Since(start))
})
}

// Error logging function
func logError(err error, context string) {
log.Printf("Error in %s: %v", context, err)
}

// Send error response
func sendErrorResponse(w http.ResponseWriter, code int, message, details string) {
errorResponse := APIError{
Code: code,
Message: message,
Details: details,
Time: time.Now().Format(time.RFC3339),
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(errorResponse)
}

// Panic recovery middleware
func panicRecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
logError(fmt.Errorf("panic: %v", err), "panic recovery")
sendErrorResponse(w, http.StatusInternalServerError, "Internal Server Error", "An unexpected error occurred")
}
}()

next.ServeHTTP(w, r)
})
}

// Error handling server
func errorHandlingServer() {
mux := http.NewServeMux()

// Test endpoint that causes panic
mux.HandleFunc("/api/panic", func(w http.ResponseWriter, r *http.Request) {
panic("Intentional panic for testing error handling")
})

// Test endpoint with validation error
mux.HandleFunc("/api/validation-error", func(w http.ResponseWriter, r *http.Request) {
sendErrorResponse(w, http.StatusBadRequest, "Validation Error", "Invalid input data")
})

// Test endpoint with not found error
mux.HandleFunc("/api/not-found", func(w http.ResponseWriter, r *http.Request) {
sendErrorResponse(w, http.StatusNotFound, "Resource Not Found", "The requested resource was not found")
})

// Test endpoint with successful response
mux.HandleFunc("/api/success", func(w http.ResponseWriter, r *http.Request) {
response := map[string]interface{}{
"message": "Success",
"data": "Operation completed successfully",
"time": time.Now().Format(time.RFC3339),
}

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})

// Apply middleware
handler := panicRecoveryMiddleware(loggingMiddleware(mux))

fmt.Println("Error handling and logging server starting on :8086")
http.ListenAndServe(":8086", handler)
}

// Start error handling server
go errorHandlingServer()
time.Sleep(2 * time.Second)

fmt.Println("Error handling and logging examples completed!")
}

What You've Learned

Congratulations! You now have a comprehensive understanding of Go's REST API development capabilities:

Basic REST API Structure

  • Understanding RESTful design principles
  • Implementing resource-based URL patterns
  • Working with HTTP methods for CRUD operations
  • Creating proper API endpoints

HTTP Methods and Status Codes

  • Using appropriate HTTP methods for different operations
  • Implementing proper HTTP status codes
  • Handling validation and error scenarios
  • Creating paginated responses

JSON Handling and Validation

  • Working with JSON encoding and decoding
  • Implementing input validation
  • Creating structured error responses
  • Handling complex JSON queries

API Versioning and Documentation

  • Implementing API versioning strategies
  • Creating comprehensive API documentation
  • Using headers for versioning
  • Maintaining backward compatibility

Error Handling and Logging

  • Implementing comprehensive error handling
  • Creating standardized error responses
  • Adding logging and monitoring
  • Implementing panic recovery

Key Concepts

  • REST API - Representational State Transfer API design
  • HTTP Methods - GET, POST, PUT, DELETE for CRUD operations
  • Status Codes - HTTP status codes for response status
  • JSON - JavaScript Object Notation for data exchange
  • Validation - Input validation and error handling

Next Steps

You now have a solid foundation in Go's REST API development capabilities. In the next section, we'll explore WebSocket communication, which will help you build real-time applications and bidirectional communication.

Understanding REST API development is crucial for building modern web services and microservices. These concepts form the foundation for all the more advanced web development techniques we'll cover in the coming sections.


Ready to learn about WebSocket communication? Let's explore real-time bidirectional communication and learn how to build WebSocket servers for real-time applications!