Documentation
¶
Overview ¶
httpx is a small helper library to work with Go's standard net/http package. The package is designed to be simple yet powerful, providing just the essential features needed for building HTTP APIs without the complexity of larger frameworks.
The main asset of httpx is its error-returning handlers and middleware pattern. Instead of handling errors within each handler, errors are returned and managed centrally by a single errorHandler function. This dramatically reduces boilerplate error handling code in your handlers.
The router and handler groups can be used standalone with the standard http.ListenAndServe or with the provided httpx.ListenAndServe function which includes graceful shutdown support.
Key Features:
- Error-returning handlers/middlewares: returns errors instead of handling them inline
- Centralized error handling: All errors managed by a single errorHandler function
- Route grouping: Organize routes with prefixes and shared middleware
- Middleware chain: Composable middleware with Next() pattern
- Graceful shutdown: Optional ListenAndServe with built-in signal handling
Index ¶
- Variables
- func DecodeQueryParams(uri url.URL, out any) error
- func JSON(w http.ResponseWriter, status int, body any) error
- func ListenAndServe(ctx context.Context, router http.Handler, serverConfs ...ServerConfFn) error
- func Next(w http.ResponseWriter, r *http.Request) error
- type ErrorHandlerFunc
- type Group
- func (g *Group) CONNECT(path string, handlers ...HandlerFunc)
- func (g *Group) DELETE(path string, handlers ...HandlerFunc)
- func (g *Group) GET(path string, handlers ...HandlerFunc)
- func (g *Group) Group(prefix string, ms ...HandlerFunc) *Group
- func (g *Group) HEAD(path string, handlers ...HandlerFunc)
- func (g *Group) OPTIONS(path string, handlers ...HandlerFunc)
- func (g *Group) PATCH(path string, handlers ...HandlerFunc)
- func (g *Group) POST(path string, handlers ...HandlerFunc)
- func (g *Group) PUT(path string, handlers ...HandlerFunc)
- func (g *Group) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (g *Group) TRACE(path string, handlers ...HandlerFunc)
- type HTTPMiddleware
- type HandlerFunc
- type LoggingFunc
- type ServerConfFn
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // IsShuttingDown is an atomic boolean flag indicating whether the server // is currently in the shutdown process. This can be used by readiness // checks to fail health checks during graceful shutdown. IsShuttingDown atomic.Bool // ErrServerHasStopped is returned when the server has initiated shutdown // and is no longer accepting new requests. ErrServerHasStopped = errors.New("server has stopped") )
Functions ¶
func DecodeQueryParams ¶
DecodeQueryParams parses URL query parameters into a struct based on field tags.
The function uses struct field tags with the key "query" to map query parameters to struct fields. It supports:
- Basic types: string, int, uint, bool, float
- Slices of basic types (e.g., ?ids=1&ids=2&ids=3)
- Nested structs using dot notation (e.g., ?address.city=Paris)
- Custom types implementing encoding.TextUnmarshaler
Tag usage:
- `query:"param_name"` - maps to the specified query parameter
- `query:"-"` - ignores the field
- no tag or empty tag - uses the field name as the parameter name
The out parameter must be a pointer to a struct
Example (NestedStruct) ¶
ExampleDecodeQueryParams_nestedStruct demonstrates using DecodeQueryParams with nested structs.
package main
import (
"fmt"
"net/url"
"github.com/quentinalbertone/httpx"
)
func main() {
// Define structs with nested structure
type Location struct {
City string `query:"city"`
Country string `query:"country"`
}
type User struct {
Name string `query:"name"`
Age int `query:"age"`
Location Location `query:"loc"`
}
// Parse a URL with nested query parameters
u, _ := url.Parse("https://api.example.com/users?name=Alice&age=30&loc.city=Paris&loc.country=France")
// Decode query parameters into the struct
var filter User
err := httpx.DecodeQueryParams(*u, &filter)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
// Use the decoded values
fmt.Printf("Name: %s\n", filter.Name)
fmt.Printf("Age: %d\n", filter.Age)
fmt.Printf("City: %s\n", filter.Location.City)
fmt.Printf("Country: %s\n", filter.Location.Country)
}
Output: Name: Alice Age: 30 City: Paris Country: France
func JSON ¶
func JSON(w http.ResponseWriter, status int, body any) error
JSON writes a JSON response with the given status code and body. It sets the Content-Type header to "application/json" and encodes the body using json.Encoder. Returns an error if the JSON encoding fails.
Example:
func GetUser(w http.ResponseWriter, r *http.Request) error {
user := User{ID: 1, Name: "John"}
return httpx.JSON(w, http.StatusOK, user)
}
Example ¶
ExampleJSON demonstrates the JSON utility function.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/quentinalbertone/httpx"
)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) error {
data := struct {
Message string `json:"message"`
Count int `json:"count"`
}{
Message: "Hello from JSON",
Count: 42,
}
return httpx.JSON(w, http.StatusOK, data)
}
// Create a test request
req := httptest.NewRequest("GET", "/test", nil)
w := httptest.NewRecorder()
// Call the handler
err := handler(w, req)
if err != nil {
panic(err)
}
resp := w.Result()
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
}
Output: Status: 200 Content-Type: application/json
func ListenAndServe ¶
ListenAndServe starts an HTTP server with graceful shutdown capabilities. It listens for SIGINT and SIGTERM signals to initiate a graceful shutdown sequence.
The shutdown sequence follows these steps:
- Upon receiving a shutdown signal or when the given context is done, sets IsShuttingDown to true
- Waits readinessDrainDelay for readiness checks to propagate
- Stops accepting new connections and waits up to shutdownPeriod for ongoing requests
- If graceful shutdown fails, waits an additional shutdownHardPeriod before returning
Returns an error if the server fails to start or encounters an error during shutdown.
Example ¶
ExampleListenAndServe demonstrates how to start an HTTP server with graceful shutdown and a CORS middleware applied globally via WithHTTPMiddleware.
package main
import (
"context"
"fmt"
"net/http"
"github.com/quentinalbertone/httpx"
)
func main() {
// Create a router with routes
router := httpx.NewRouter(nil, nil)
router.GET("/api/users", func(w http.ResponseWriter, r *http.Request) error {
return httpx.JSON(w, http.StatusOK, []string{"Alice", "Bob"})
})
// CORSMiddleware sets CORS headers on every response and handles preflight requests.
corsMiddleware := func(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, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// Configure the server with custom settings
serverConf := func(server *http.Server) {
server.Addr = ":8080"
// You can set other server options here:
// server.ReadTimeout = 5 * time.Second
// server.WriteTimeout = 10 * time.Second
// server.IdleTimeout = 120 * time.Second
}
// Start the server with CORS enabled globally.
// WithHTTPMiddleware wraps the server's handler so it applies to all
// requests, including those that don't match any route.
ctx := context.Background()
if err := httpx.ListenAndServe(ctx, router,
serverConf,
httpx.WithHTTPMiddleware(corsMiddleware),
); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
func Next ¶
func Next(w http.ResponseWriter, r *http.Request) error
Next calls the next handler in the middleware chain. Middleware should call Next to continue the request processing. If there is no next handler, Next returns nil.
Example:
func MyMiddleware(w http.ResponseWriter, r *http.Request) error {
// Do something before
err := httpx.Next(w, r)
// Do something after
return err
}
Example ¶
ExampleNext demonstrates middleware usage with the Next function.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/quentinalbertone/httpx"
)
func main() {
// Custom middleware that adds a header
headerMiddleware := func(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("X-Custom-Header", "middleware-value")
// Call the next handler in the chain
return httpx.Next(w, r)
}
router := httpx.NewRouter(nil, nil)
// Add a route with middleware
router.GET("/protected", headerMiddleware, func(w http.ResponseWriter, r *http.Request) error {
return httpx.JSON(w, http.StatusOK, map[string]string{
"message": "This response has a custom header",
})
})
server := httptest.NewServer(router)
defer server.Close()
resp, err := http.Get(server.URL + "/protected")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Custom Header:", resp.Header.Get("X-Custom-Header"))
}
Output: Custom Header: middleware-value
Types ¶
type ErrorHandlerFunc ¶
type ErrorHandlerFunc func(HandlerFunc) http.HandlerFunc
ErrorHandlerFunc converts a HandlerFunc (which returns an error) into a standard http.HandlerFunc. It is responsible for handling any errors returned by the HandlerFunc and writing appropriate HTTP responses.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group represents a router group that can have a prefix, middlewares, and sub-groups. It implements http.Handler and can be used directly with http.Server.
Example ¶
ExampleGroup demonstrates route grouping and middleware.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/quentinalbertone/httpx"
)
func main() {
router := httpx.NewRouter(nil, nil)
// Create an API group with a common prefix
api := router.Group("/api")
// Create a v1 subgroup
v1 := api.Group("/v1")
// Add routes to the v1 group
v1.GET("/users", func(w http.ResponseWriter, r *http.Request) error {
users := []map[string]interface{}{
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
}
return httpx.JSON(w, http.StatusOK, users)
})
v1.POST("/users", func(w http.ResponseWriter, r *http.Request) error {
return httpx.JSON(w, http.StatusCreated, map[string]string{
"message": "User created",
})
})
server := httptest.NewServer(router)
defer server.Close()
// Test the grouped route
resp, err := http.Get(server.URL + "/api/v1/users")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
}
Output: Status: 200
func NewRouter ¶
func NewRouter(logger LoggingFunc, errorHandler ErrorHandlerFunc, ms ...HandlerFunc) *Group
NewRouter creates a new root router with optional global middlewares and logger. The logger wraps all routes for logging HTTP requests and responses. The errorHandler converts HandlerFunc errors into HTTP responses.
Example ¶
ExampleNewRouter demonstrates basic usage of the httpx router.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"github.com/quentinalbertone/httpx"
)
func main() {
// Create a new router with default logging and error handling
router := httpx.NewRouter(nil, nil)
// Add a simple GET route
router.GET("/hello", func(w http.ResponseWriter, r *http.Request) error {
return httpx.JSON(w, http.StatusOK, map[string]string{
"message": "Hello, World!",
})
})
// Create a test server
server := httptest.NewServer(router)
defer server.Close()
// Make a request to test the route
resp, err := http.Get(server.URL + "/hello")
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
}
Output: Status: 200 Content-Type: application/json
func (*Group) CONNECT ¶
func (g *Group) CONNECT(path string, handlers ...HandlerFunc)
CONNECT registers a CONNECT route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) DELETE ¶
func (g *Group) DELETE(path string, handlers ...HandlerFunc)
DELETE registers a DELETE route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) GET ¶
func (g *Group) GET(path string, handlers ...HandlerFunc)
GET registers a GET route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) Group ¶
func (g *Group) Group(prefix string, ms ...HandlerFunc) *Group
Group creates a new subgroup with a prefix and optional middlewares. The subgroup inherits the parent's mux, logger, and errorHandler. Middlewares from the parent group are combined with the new middlewares.
func (*Group) HEAD ¶
func (g *Group) HEAD(path string, handlers ...HandlerFunc)
HEAD registers a HEAD route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) OPTIONS ¶
func (g *Group) OPTIONS(path string, handlers ...HandlerFunc)
OPTIONS registers a OPTIONS route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) PATCH ¶
func (g *Group) PATCH(path string, handlers ...HandlerFunc)
PATCH registers a PATCH route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) POST ¶
func (g *Group) POST(path string, handlers ...HandlerFunc)
POST registers a POST route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) PUT ¶
func (g *Group) PUT(path string, handlers ...HandlerFunc)
PUT registers a PUT route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
func (*Group) ServeHTTP ¶
func (g *Group) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements the http.Handler interface. This allows Group to be used directly with http.Server.
Example:
router := httpx.NewRouter(nil, nil)
http.ListenAndServe(":8080", router)
func (*Group) TRACE ¶
func (g *Group) TRACE(path string, handlers ...HandlerFunc)
TRACE registers a TRACE route with optional middlewares and a final handler. Middlewares are executed in the order provided, followed by the final handler.
type HTTPMiddleware ¶
HTTPMiddleware is a standard HTTP middleware that wraps an http.Handler. Unlike HandlerFunc middleware which is part of the route chain and returns errors, HTTPMiddleware runs before routing and follows the standard middleware pattern.
type HandlerFunc ¶
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
HandlerFunc is an HTTP handler that returns an error. This allows handlers to return errors which can be handled centrally by an ErrorHandlerFunc.
type LoggingFunc ¶
LoggingFunc is a function that wraps an http.Handler to add logging functionality. It follows the standard middleware pattern for http.Handler.
type ServerConfFn ¶
ServerConfFn is a function type used to configure an http.Server instance before starting it. This allows callers to customize server settings such as timeouts, TLS configuration, and other http.Server fields.
func WithHTTPMiddleware ¶
func WithHTTPMiddleware(middlewares ...HTTPMiddleware) ServerConfFn
WithHTTPMiddleware returns a ServerConfFn that wraps the server's handler with the given HTTP middlewares. Middlewares are applied so that they execute in the order they are provided (first middleware runs first). This is useful for functionality like CORS, request ID generation, or metrics that should apply to all requests including those that don't match any route.
Example:
httpx.ListenAndServe(ctx, router,
httpx.WithHTTPMiddleware(CORSMiddleware, RequestIDMiddleware),
)