Middleware: Lapisan yang Menyelamatkan
Middleware di Go itu seperti security checkpoint di gedung: setiap request harus melewati beberapa lapisan sebelum sampai ke handler. Authentication, logging, rate limiting — semua bisa di-stack melalui middleware. Dan yang paling powerful: middleware bisa di-reuse di berbagai endpoint tanpa duplicate code.
Saya biasanya pakai Fiber atau Echo untuk web framework di Go, tapi middleware pattern-nya universal.
1. Middleware Dasar
func LoggerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("Completed %s %s in %v",
r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", handleUsers)
var handler http.Handler = mux
handler = RecoveryMiddleware(handler)
handler = AuthMiddleware(handler)
handler = CORSMiddleware(handler)
handler = LoggerMiddleware(handler)
http.ListenAndServe(":8080", handler)
}
2. Authentication Middleware
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isPublicRoute(r.URL.Path) {
next.ServeHTTP(w, r)
return
}
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
claims, err := validateToken(token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "user_id", claims.UserID)
ctx = context.WithValue(ctx, "user_role", claims.Role)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
3. Rate Limiting Middleware
Dari pengalaman saya, rate limiting adalah middleware paling penting untuk production API. Tanpa ini, satu user bisa overload seluruh sistem:
type RateLimiter struct {
limiter *rate.Limiter
}
func NewRateLimiter(rps rate.Limit, burst int) *RateLimiter {
return &RateLimiter{
limiter: rate.NewLimiter(rps, burst),
}
}
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !rl.limiter.Allow() {
w.Header().Set("Retry-After", "1")
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
func (rl *RateLimiter) PerUserMiddleware(next http.Handler) http.Handler {
users := make(map[string]*rate.Limiter)
var mu sync.Mutex
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r)
mu.Lock()
if _, exists := users[userID]; !exists {
users[userID] = rate.NewLimiter(10, 20)
}
limiter := users[userID]
mu.Unlock()
if !limiter.Allow() {
http.Error(w, "Too many requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
4. Panic Recovery Middleware
Di production, panic yang tidak di-recover bisa crash seluruh server. Ini middleware yang wajib ada:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Error("Panic recovered",
"error", err,
"stack", string(debug.Stack()),
"path", r.URL.Path,
)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
"error": "Internal server error",
})
}
}()
next.ServeHTTP(w, r)
})
}
Best Practices dari Production
1. Middleware ordering matters. Recovery harus paling outer (first execute), authentication di atasnya, dan logging di luar semua.
2. Keep middleware small. Setiap middleware harus punya satu responsibility. Jangan gabungkan auth + logging di satu middleware.
3. Context propagation. Gunakan context untuk membawa data antar middleware. Jangan gunakan global variables — ini tidak thread-safe.
4. Test middleware independently. Setiap middleware bisa di-test dengan HTTP request/response yang di-mock tanpa perlu menjalankan full server.