Keratin is a thin, idiomatic Go wrapper around http.ServeMux that provides a clean, composable API for building HTTP servers with route grouping, middleware support, and error handling—all while maintaining zero dependencies beyond the Go standard library and minimal external packages for testing and UUID generation.
- Route Grouping: Organize routes into logical groups with shared prefixes and middleware
- Flexible Middleware: Apply middleware at router, group, or route level with priority-based execution
- Pre-Middleware: Register middleware that executes before route matching
- Error Handling: Centralized error handling with custom error handlers
- Type-Safe: Built-in types for handlers, middleware, and error handlers
- Method Shorthands: Convenience methods for all HTTP verbs (GET, POST, PUT, DELETE, PATCH, etc.)
- Zero Abstraction Leak: Leverages
http.ServeMuxpattern matching without re-implementing routing logic - Minimal Dependencies: Only uses
github.com/google/uuidfor middleware IDs andgithub.com/stretchr/testifyfor testing
Just as keratin provides the essential structure that makes wool strong, flexible, and resilient, Keratin provides the essential structure for building robust HTTP services in the Gowool ecosystem. While Go's http.ServeMux is powerful, it lacks built-in support for route grouping and middleware at the router level. Keratin fills this gap by providing a thin, composable wrapper that:
- Maintains the simplicity and performance of the standard library
- Adds essential features for building production-grade HTTP services
- Follows idiomatic Go patterns and conventions
- Provides a clean, fluent API for route definition
go get github.com/gowool/keratinpackage main
import (
"log/slog"
"net/http"
"github.com/gowool/keratin"
)
func main() {
router := keratin.NewRouter()
router.GET("/health", func(w http.ResponseWriter, _ *http.Request) error {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("OK"))
return err
})
api := router.Group("/api")
api.UseFunc(loggingMiddleware)
v1 := api.Group("/v1")
v1.Route(http.MethodGet, "/users", listUsers())
v1.Route(http.MethodPost, "/users", createUser())
_ = http.ListenAndServe(":8080", router.Build())
}
func loggingMiddleware(next keratin.Handler) keratin.Handler {
return keratin.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
defer slog.Info("request",
slog.String("method", r.Method),
slog.String("protocol", r.Proto),
slog.String("host", r.Host),
slog.String("pattern", r.Pattern),
slog.String("uri", r.RequestURI),
slog.String("path", r.URL.Path),
slog.String("referer", r.Referer()),
slog.String("user_agent", r.UserAgent()),
)
return next.ServeHTTP(w, r)
})
}
func listUsers() keratin.Handler {
return keratin.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) error {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("users"))
return err
})
}
func createUser() keratin.Handler {
return keratin.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) error {
w.WriteHeader(http.StatusCreated)
return nil
})
}This project is licensed under the MIT License - see the LICENSE file for details.