advanced Step 15 of 15

Middleware and Database Integration

Go Programming

Middleware and Database Integration

Production Go APIs require middleware for cross-cutting concerns and a database for persistent storage. Middleware functions wrap HTTP handlers to add logging, authentication, CORS, rate limiting, and error recovery. For database access, Go's database/sql package provides a generic interface, while libraries like sqlx and ORMs like GORM add convenience features. This lesson covers production patterns for building robust, database-backed Go APIs.

Middleware Chain

package main

import (
    "log"
    "net/http"
    "time"
)

type Middleware func(http.Handler) http.Handler

func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        handler = middlewares[i](handler)
    }
    return handler
}

func Logger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

func CORS(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")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        if r.Method == "OPTIONS" {
            w.WriteHeader(204)
            return
        }
        next.ServeHTTP(w, r)
    })
}

func Recovery(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                log.Printf("Panic recovered: %v", err)
                http.Error(w, "Internal Server Error", 500)
            }
        }()
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := http.NewServeMux()
    // ... register routes ...

    handler := Chain(mux, Logger, CORS, Recovery)
    log.Fatal(http.ListenAndServe(":8080", handler))
}

Database Integration

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"  // MySQL driver
)

func initDB() (*sql.DB, error) {
    db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/mydb?parseTime=true")
    if err != nil {
        return nil, err
    }
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(5)
    db.SetConnMaxLifetime(5 * time.Minute)

    if err := db.Ping(); err != nil {
        return nil, err
    }
    return db, nil
}

type UserRepo struct {
    db *sql.DB
}

func (r *UserRepo) FindAll() ([]User, error) {
    rows, err := r.db.Query("SELECT id, name, email FROM users")
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
            return nil, err
        }
        users = append(users, u)
    }
    return users, rows.Err()
}

func (r *UserRepo) Create(name, email string) (int64, error) {
    result, err := r.db.Exec(
        "INSERT INTO users (name, email) VALUES (?, ?)",
        name, email,
    )
    if err != nil {
        return 0, err
    }
    return result.LastInsertId()
}
Pro tip: Always close sql.Rows with defer rows.Close() and check rows.Err() after iteration. Configure connection pool settings (SetMaxOpenConns, SetMaxIdleConns) based on your database server's capacity. Use prepared statements for frequently executed queries to improve performance.

Key Takeaways

  • Middleware functions wrap handlers to add logging, CORS, authentication, and panic recovery.
  • Chain middleware in order: Recovery (outermost) -> Logger -> CORS -> Auth -> Handler.
  • Use database/sql with driver imports for database access; configure connection pooling.
  • Always close sql.Rows, check errors after iteration, and use parameterized queries.
  • Dependency injection (passing *sql.DB to repositories) makes code testable and decoupled.
arrow_back Building a REST API check_circle Lap Complete!