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 closesql.Rowswithdefer rows.Close()and checkrows.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/sqlwith driver imports for database access; configure connection pooling. - Always close
sql.Rows, check errors after iteration, and use parameterized queries. - Dependency injection (passing
*sql.DBto repositories) makes code testable and decoupled.
arrow_back Building a REST API
check_circle Lap Complete!