zerohttp

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2025 License: MIT Imports: 23 Imported by: 0

README

zerohttp Go Report Card codecov

⚠️ This is a pre-v1 release - APIs may change as we work toward a stable v1.0.

A lightweight HTTP framework for Go built on top of the standard net/http library. Designed for simplicity, developer productivity, and security.

Table of Contents

Features

  • Lightweight: Built on Go's standard net/http with minimal overhead
  • Zero Dependencies: No external dependencies except golang.org/x/crypto for AutoTLS
  • Secure by Default: Automatically applies essential security middlewares out of the box
  • Response Rendering: Built-in support for JSON, HTML, text, and file responses
  • Request Binding: JSON request body parsing
  • Problem Details: RFC 9457 Problem Details for HTTP APIs error responses
  • Flexible Routing: Method-based routing with route groups and parameter support
  • Middleware Support: Comprehensive middleware system with built-in security, logging, and utility middlewares
  • Built-in Security: CORS, rate limiting, request body size limits, security headers, and more
  • Auto-TLS: Built-in Let's Encrypt support with automatic certificate management
  • Request Tracing: Built-in request ID generation and propagation
  • Circuit Breaker: Prevent cascading failures with configurable circuit breaker middleware
  • Structured Logging: Integrated structured logging with customizable fields
  • Health Checks: Kubernetes-compatible health check endpoints with customizable handlers

Requirements

  • Go 1.23 or later
  • No external dependencies (except golang.org/x/crypto for AutoTLS features)

Secure by Default

zerohttp applies security best practices automatically with these default middlewares:

  • Request ID: Generates unique request IDs for tracing and debugging
  • Panic Recovery: Gracefully handles panics with stack trace logging
  • Request Body Size Limits: Prevents DoS attacks from large request bodies
  • Security Headers: Sets essential security headers (CSP, HSTS, X-Frame-Options, etc.)
  • Request Logging: Comprehensive request/response logging with security context

These middlewares are enabled by default but can be customized or disabled as needed.

Installation

go get github.com/alexferl/zerohttp

Quick Start

package main

import (
    "encoding/json"
    "log"
    "net/http"

    zh "github.com/alexferl/zerohttp"
)

func main() {
    app := zh.New()

    // Using standard net/http - full control
    app.GET("/hello-std", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusOK)

        response := map[string]string{"message": "Hello from standard library!"}
        json.NewEncoder(w).Encode(response)
    }))

    // Using zerohttp helpers - more concise
    app.GET("/hello", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
        return zh.Render.JSON(w, 200, zh.M{"message": "Hello from zerohttp!"})
    }))

    log.Fatal(app.Start())
}

Response Rendering

Clean, extensible interfaces for all response types:

// JSON responses (most common)
zh.Render.JSON(w, 200, zh.M{"message": "Hello, World!"})

// Text responses
zh.Render.Text(w, 200, "Plain text response")

// HTML responses
zh.Render.HTML(w, 200, "<h1>Welcome</h1>")

// Binary data
zh.Render.Blob(w, 200, "image/png", pngData)

// Streaming responses
zh.Render.Stream(w, 200, "text/plain", reader)

// File serving with proper headers
zh.Render.File(w, r, "path/to/document.pdf")

// RFC 9457 Problem Details
problem := zh.NewProblemDetail(404, "User not found")
problem.Set("user_id", "123")
zh.Render.ProblemDetail(w, problem)

Short alias available: Use zh.R instead of zh.Render for brevity.

Request Binding

Simple JSON request parsing with validation:

app.POST("/api/users", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    var user struct {
        Name  string `json:"name"`
        Email string `json:"email"`
        Age   int    `json:"age"`
    }

    // Bind JSON with unknown field validation
    if err := zh.Bind.JSON(r.Body, &user); err != nil {
        problem := zh.NewProblemDetail(400, "Invalid request body")
        problem.Set("error", err.Error())
        return zh.Render.ProblemDetail(w, problem)
    }

    // Process user...
    return zh.Render.JSON(w, 201, user)
}))

Short alias available: Use zh.B instead of zh.Bind for convenience.

The binder uses json.Decoder with DisallowUnknownFields() for stricter validation.

Middleware

zerohttp includes a comprehensive set of built-in middlewares:

app := zh.New()

// Add additional middleware (default security middlewares already applied)
app.Use(
    middleware.CORS(
        config.WithCORSAllowedOrigins([]string{"https://example.com"}),
        config.WithCORSAllowCredentials(true),
    ),
    middleware.RateLimit(
        config.WithRateLimitRate(100),
        config.WithRateLimitWindow(time.Minute),
    ),
)

// Route-specific middleware
app.GET("/admin", adminHandler,
    middleware.BasicAuth(
        config.WithBasicAuthCredentials(map[string]string{"admin": "secret"}),
    ),
    middleware.RequestBodySize(
        config.WithRequestBodySizeMaxBytes(1024 * 1024), // 1MB limit
    ),
)

Route Groups

Organize your routes with groups:

app.Group(func(api zh.Router) {
    api.Use(middleware.RequireAuth())

    api.GET("/users", listUsers)
    api.POST("/users", createUser)
    api.PUT("/users/{id}", updateUser)
    api.DELETE("/users/{id}", deleteUser)
})

Static File Serving

Serve static files from embedded filesystems or directories:

//go:embed static
var staticFiles embed.FS

//go:embed dist
var appFiles embed.FS

app := zh.New()

// API routes
app.GET("/api/health", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    return zh.R.JSON(w, 200, zh.M{"status": "healthy"})
}))

// Serve static assets (CSS, JS, images) from embedded FS
app.Files("/static/", staticFiles, "static")

// Serve files from directory (uploads, user content)
app.FilesDir("/uploads/", "./uploads")

// Serve web application with client-side routing support
app.Static(appFiles, "dist", "/api/")

// Or serve from directory for development
// app.StaticDir("./dist", "/api/")

log.Fatal(app.Start())
Static File Methods
  • Files() - Serves files from embedded FS without fallback
  • FilesDir() - Serves files from directory without fallback
  • Static() - Serves web app from embedded FS with index.html fallback for client-side routing
  • StaticDir() - Serves web app from directory with index.html fallback for client-side routing

The Static methods support API prefix exclusions - requests matching specified prefixes return 404 instead of falling back to index.html, allowing API and static routes to coexist.

Error Handling

Built-in support for RFC 9457 Problem Details:

app.GET("/error", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    problem := zh.NewProblemDetail(400, "Invalid request")
    problem.Set("field", "email")
    problem.Set("reason", "Email address is required")
    return zh.R.ProblemDetail(w, problem)
}))
Validation Errors

Built-in support for validation error responses:

// Using default validation errors
errors := []zh.ValidationError{
    {Detail: "must be a valid email", Pointer: "#/email"},
    {Detail: "must be at least 8 characters", Field: "password"},
}
problem := zh.NewValidationProblemDetail("Validation failed", errors)
return zh.R.ProblemDetail(w, problem)

// Using custom error structures
type CustomError struct {
    Code    string `json:"code"`
    Field   string `json:"field"`
    Message string `json:"message"`
}

customErrors := []CustomError{
    {Code: "INVALID_EMAIL", Field: "email", Message: "Email format is invalid"},
}
problem := zh.NewValidationProblemDetail("Validation failed", customErrors)
return zh.R.ProblemDetail(w, problem)

Configuration

Flexible configuration system with functional options:

app := zh.New(
    // Server configuration
    config.WithAddr(":8080"),
    config.WithServer(&http.Server{
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }),
    config.WithTLSAddr(":8443"),
    config.WithTLSServer(&http.Server{
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
    }),
    config.WithCertFile("cert.pem"),
    config.WithKeyFile("key.pem"),
    config.WithLogger(myCustomLogger),

    // Configure default middlewares using their respective option containers
    config.WithRequestBodySizeOptions(
        config.WithRequestBodySizeMaxBytes(10*1024*1024), // 10MB
    ),
    config.WithRequestIDOptions(
        config.WithRequestIDHeader("X-Request-ID"),
    ),
    config.WithRecoverOptions(
        config.WithRecoverStackSize(8192),
        config.WithRecoverEnableStackTrace(true),
    ),
    config.WithSecurityHeadersOptions(
        config.WithSecurityHeadersCSP("default-src 'self'"),
        config.WithSecurityHeadersXFrameOptions("SAMEORIGIN"),
        config.WithSecurityHeadersHSTS(
            config.WithHSTSMaxAge(31536000), // 1 year
            config.WithHSTSPreload(true),
        ),
    ),
)

Disabling Default Security

If you need to disable default middlewares:

app := zh.New(
    config.WithDisableDefaultMiddlewares(), // Disable all defaults
    // Or provide custom defaults
    config.WithDefaultMiddlewares([]func(http.Handler) http.Handler{
        middleware.RequestID(),
        middleware.CORS(),
    }),
)

Available Middlewares

  • Authentication: Basic Auth
  • Security: CORS, Security Headers, Request Body Size Limits
  • Rate Limiting: Rate Limit with configurable algorithms
  • Content Handling: Compress, Content Charset, Content Encoding, Content Type
  • Monitoring: Request Logger, Circuit Breaker, Timeout, Recover
  • Utilities: Request ID, Real IP, Trailing Slash, Set Header, No Cache, With Value

Each middleware uses functional options for configuration:

// CORS middleware
middleware.CORS(
    config.WithCORSAllowedOrigins([]string{"https://example.com"}),
    config.WithCORSAllowCredentials(true),
)

// Rate limiting
middleware.RateLimit(
    config.WithRateLimitRate(50),
    config.WithRateLimitWindow(time.Minute),
    config.WithRateLimitAlgorithm(config.TokenBucket),
)

// Compression
middleware.Compress(
    config.WithCompressLevel(6),
)

// Security headers with HSTS options
middleware.SecurityHeaders(
    config.WithSecurityHeadersCSP("default-src 'self'; script-src 'self' 'unsafe-inline'"),
    config.WithSecurityHeadersHSTS(
        config.WithHSTSMaxAge(31536000),
        config.WithHSTSPreload(true),
    ),
)

Extensible Interfaces

Both rendering and binding use interfaces, making them easy to customize:

// Custom renderer
type MyRenderer struct{}

func (r *MyRenderer) JSON(w http.ResponseWriter, code int, data any) error {
    // Custom JSON rendering logic
    w.Header().Set("X-Custom-JSON", "true")
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    return json.NewEncoder(w).Encode(data)
}

// Replace default
zh.Render = &MyRenderer{}

// Custom binder
type MyBinder struct{}

func (b *MyBinder) JSON(r io.Reader, dst any) error {
    // Custom JSON binding logic
    decoder := json.NewDecoder(r)
    decoder.UseNumber() // Use json.Number instead of float64
    return decoder.Decode(dst)
}

// Replace default
zh.Bind = &MyBinder{}

Auto-TLS with Let's Encrypt

app := zh.New(
    config.WithAutocertManager(zh.NewAutocertManager("/tmp/certs", "example.com")),
)

app.StartAutoTLS("example.com", "www.example.com")

Health Checks

Add Kubernetes-compatible health check endpoints with minimal setup:

import (
    "log"
    "net/http"

    zh "github.com/alexferl/zerohttp"
    "github.com/alexferl/zerohttp/healthcheck"
)

func main() {
    app := zh.New()

    // Add default health endpoints: /livez, /readyz, /startupz
    healthcheck.New(app)

    // Or customize endpoints and handlers
    healthcheck.New(app,
        healthcheck.WithLivenessEndpoint("/health/live"),
        healthcheck.WithReadinessEndpoint("/health/ready"),
        healthcheck.WithReadinessHandler(func(w http.ResponseWriter, r *http.Request) error {
            // Check database connections, dependencies, etc.
            if !isAppReady() {
                return zh.R.Text(w, http.StatusServiceUnavailable, "not ready")
            }
            return zh.R.Text(w, http.StatusOK, "ready")
        }),
        healthcheck.WithStartupEndpoint("/health/startup"),
    )

    log.Fatal(app.Start())
}

The health check package provides three standard endpoints:

  • /livez - Liveness probe (is the app running?)
  • /readyz - Readiness probe (is the app ready to handle traffic?)
  • /startupz - Startup probe (has the app finished initializing?)

Circuit Breaker

Prevent cascading failures with configurable circuit breaker middleware:

// Basic circuit breaker - breaks after 5 failures, recovers after 30s
app.Use(middleware.CircuitBreaker())

// Custom configuration
app.Use(middleware.CircuitBreaker(
    config.WithCircuitBreakerFailureThreshold(3),             // Break after 3 failures
    config.WithCircuitBreakerRecoveryTimeout(10*time.Second), // Try recovery after 10s
    config.WithCircuitBreakerOpenStatusCode(503),             // Return 503 when open
))

The circuit breaker operates in three states: Closed (normal), Open (blocked), and Half-Open (testing recovery). It prevents cascading failures when downstream services are unavailable.

Configuration Reference

The functional options pattern provides structured configuration for all aspects of the server:

Server Configuration
  • config.WithAddr() - HTTP server address
  • config.WithTLSAddr() - HTTPS server address
  • config.WithServer() - HTTP server settings
  • config.WithTLSServer() - HTTPS server settings
  • config.WithListener()/config.WithTLSListener() - Custom listeners
  • config.WithCertFile()/config.WithKeyFile() - TLS certificate files
  • config.WithAutocertManager() - Let's Encrypt integration
Middleware Configuration
  • config.WithDisableDefaultMiddlewares() - Disable built-in middlewares
  • config.WithDefaultMiddlewares() - Custom middleware chain
  • config.WithRequestIDOptions() - Request ID generation settings
  • config.WithRecoverOptions() - Panic recovery settings
  • config.WithRequestBodySizeOptions() - Request body size limits
  • config.WithSecurityHeadersOptions() - Security header configuration options
  • config.WithRequestLoggerOptions() - Request logging configuration
Logging
  • config.WithLogger() - Custom logger instance

Documentation

Index

Constants

View Source
const (
	MIMETextHTML           = "text/html; charset=utf-8"
	MIMETextPlain          = "text/plain; charset=utf-8"
	MIMEApplicationJSON    = "application/json; charset=utf-8"
	MIMEApplicationProblem = "application/problem+json"
)
View Source
const (
	HeaderAccept            = "Accept"
	HeaderAcceptCharset     = "Accept-Charset"
	HeaderAcceptEncoding    = "Accept-Encoding"
	HeaderAcceptLanguage    = "Accept-Language"
	HeaderAcceptRanges      = "Accept-Ranges"
	HeaderAuthorization     = "Authorization"
	HeaderCacheControl      = "Cache-Control"
	HeaderConnection        = "Connection"
	HeaderContentLength     = "Content-Length"
	HeaderContentType       = "Content-Type"
	HeaderCookie            = "Cookie"
	HeaderDate              = "Date"
	HeaderExpect            = "Expect"
	HeaderForwarded         = "Forwarded"
	HeaderFrom              = "From"
	HeaderHost              = "Host"
	HeaderIfMatch           = "If-Match"
	HeaderIfModifiedSince   = "If-Modified-Since"
	HeaderIfNoneMatch       = "If-None-Match"
	HeaderIfRange           = "If-Range"
	HeaderIfUnmodifiedSince = "If-Unmodified-Since"
	HeaderMaxForwards       = "Max-Forwards"
	HeaderOrigin            = "Origin"
	HeaderPragma            = "Pragma"
	HeaderRange             = "Range"
	HeaderReferer           = "Referer"
	HeaderTE                = "TE"
	HeaderUserAgent         = "User-Agent"
	HeaderUpgrade           = "Upgrade"
	HeaderVia               = "Via"
	HeaderWarning           = "Warning"
)

Request Headers

View Source
const (
	HeaderAcceptPatch        = "Accept-Patch"
	HeaderAge                = "Age"
	HeaderAllow              = "Allow"
	HeaderContentDisposition = "Content-Disposition"
	HeaderContentEncoding    = "Content-Encoding"
	HeaderContentLanguage    = "Content-Language"
	HeaderContentLocation    = "Content-Location"
	HeaderContentRange       = "Content-Range"
	HeaderETag               = "ETag"
	HeaderExpires            = "Expires"
	HeaderLastModified       = "Last-Modified"
	HeaderLink               = "Link"
	HeaderLocation           = "Location"
	HeaderProxyAuthenticate  = "Proxy-Authenticate"
	HeaderRetryAfter         = "Retry-After"
	HeaderServer             = "Server"
	HeaderSetCookie          = "Set-Cookie"
	HeaderTrailer            = "Trailer"
	HeaderTransferEncoding   = "Transfer-Encoding"
	HeaderVary               = "Vary"
	HeaderWWWAuthenticate    = "WWW-Authenticate"
)

Response Headers

View Source
const (
	HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
	HeaderAccessControlAllowHeaders     = "Access-Control-Allow-Headers"
	HeaderAccessControlAllowMethods     = "Access-Control-Allow-Methods"
	HeaderAccessControlAllowOrigin      = "Access-Control-Allow-Origin"
	HeaderAccessControlExposeHeaders    = "Access-Control-Expose-Headers"
	HeaderAccessControlMaxAge           = "Access-Control-Max-Age"
	HeaderAccessControlRequestHeaders   = "Access-Control-Request-Headers"
	HeaderAccessControlRequestMethod    = "Access-Control-Request-Method"
)

CORS Headers

View Source
const (
	HeaderContentSecurityPolicy           = "Content-Security-Policy"
	HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
	HeaderCrossOriginEmbedderPolicy       = "Cross-Origin-Embedder-Policy"
	HeaderCrossOriginOpenerPolicy         = "Cross-Origin-Opener-Policy"
	HeaderCrossOriginResourcePolicy       = "Cross-Origin-Resource-Policy"
	HeaderFeaturePolicy                   = "Feature-Policy"
	HeaderPermissionsPolicy               = "Permissions-Policy"
	HeaderReferrerPolicy                  = "Referrer-Policy"
	HeaderSecFetchSite                    = "Sec-Fetch-Site"
	HeaderStrictTransportSecurity         = "Strict-Transport-Security"
	HeaderXContentTypeOptions             = "X-Content-Type-Options"
	HeaderXFrameOptions                   = "X-Frame-Options"
	HeaderXXSSProtection                  = "X-XSS-Protection"
)

Security Headers

View Source
const (
	HeaderXAPIKey             = "X-API-Key"
	HeaderXCSRFToken          = "X-CSRF-Token"
	HeaderXForwardedFor       = "X-Forwarded-For"
	HeaderXForwardedHost      = "X-Forwarded-Host"
	HeaderXForwardedProto     = "X-Forwarded-Proto"
	HeaderXForwardedProtocol  = "X-Forwarded-Protocol"
	HeaderXForwardedSsl       = "X-Forwarded-Ssl"
	HeaderXRealIP             = "X-Real-IP"
	HeaderXRequestID          = "X-Request-ID"
	HeaderXRequestedWith      = "X-Requested-With"
	HeaderXPoweredBy          = "X-Powered-By"
	HeaderXRateLimitLimit     = "X-RateLimit-Limit"
	HeaderXRateLimitRemaining = "X-RateLimit-Remaining"
	HeaderXRateLimitReset     = "X-RateLimit-Reset"
)

Custom/Extension Headers

View Source
const (
	HeaderUpgradeWebSocket       = "websocket"
	HeaderSecWebSocketKey        = "Sec-WebSocket-Key"
	HeaderSecWebSocketAccept     = "Sec-WebSocket-Accept"
	HeaderSecWebSocketVersion    = "Sec-WebSocket-Version"
	HeaderSecWebSocketProtocol   = "Sec-WebSocket-Protocol"
	HeaderSecWebSocketExtensions = "Sec-WebSocket-Extensions"
)

WebSocket Headers

View Source
const (
	AuthSchemeBasic  = "Basic"
	AuthSchemeBearer = "Bearer"
	AuthSchemeDigest = "Digest"
	AuthSchemeOAuth  = "OAuth"
)

Authentication Schemes

View Source
const (
	ConnectionClose     = "close"
	ConnectionKeepAlive = "keep-alive"
	ConnectionUpgrade   = "Upgrade"

	CacheControlNoCache        = "no-cache"
	CacheControlNoStore        = "no-store"
	CacheControlMustRevalidate = "must-revalidate"
	CacheControlPublic         = "public"
	CacheControlPrivate        = "private"
	CacheControlMaxAge         = "max-age"

	ContentEncodingGzip    = "gzip"
	ContentEncodingDeflate = "deflate"
	ContentEncodingBrotli  = "br"

	TransferEncodingChunked = "chunked"
)

Common Header Values

Variables

View Source
var B = Bind

B is a short alias for Bind for convenience

R is a short alias for Render for convenience

Functions

func NewAutocertManager

func NewAutocertManager(cacheDir string, hosts ...string) *autocert.Manager

NewAutocertManager creates a new autocert manager with the given cache directory and hosts. The manager handles automatic certificate provisioning and renewal using Let's Encrypt's ACME protocol.

Parameters:

  • cacheDir: Directory path where certificates and ACME account information will be cached. This directory should be persistent across server restarts.
  • hosts: List of hostnames for which certificates should be automatically obtained. Only requests for these hosts will be served with auto-generated certificates.

The returned manager is configured to:

  • Accept Let's Encrypt Terms of Service automatically
  • Use directory-based caching for certificates
  • Restrict certificate generation to the specified hosts

Example usage:

manager := NewAutocertManager("/var/cache/certs", "example.com", "www.example.com")
server := zerohttp.New(config.WithAutocertManager(manager))

Types

type Binder

type Binder interface {
	// JSON decodes JSON request body into the destination struct.
	// It uses json.NewDecoder with DisallowUnknownFields enabled
	// for safer JSON parsing that rejects unknown fields.
	JSON(r io.Reader, dst any) error
}

Binder handles request binding and parsing for various content types. It provides methods to decode request data into Go structs.

var Bind Binder = &defaultBinder{}

Bind is the default binder instance used by the package

type HandlerFunc

type HandlerFunc func(w http.ResponseWriter, r *http.Request) error

HandlerFunc is a handler function that returns an error

func (HandlerFunc) ServeHTTP

func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler interface

type M

type M map[string]any

M is a convenience type for map[string]any, useful for quick JSON responses

type ProblemDetail

type ProblemDetail struct {
	// Type is a URI reference that identifies the problem type
	Type string `json:"type,omitempty"`

	// Title is a short, human-readable summary of the problem type
	Title string `json:"title"`

	// Status is the HTTP status code for this occurrence of the problem
	Status int `json:"status"`

	// Detail is a human-readable explanation specific to this occurrence
	Detail string `json:"detail,omitempty"`

	// Instance is a URI reference that identifies the specific occurrence
	Instance string `json:"instance,omitempty"`

	// Extensions contains additional problem-specific data
	Extensions map[string]any `json:"-"`
}

ProblemDetail represents an RFC 9457 Problem Details response. It provides a standardized way to carry machine-readable details of errors in HTTP response bodies.

func NewProblemDetail

func NewProblemDetail(statusCode int, detail string) *ProblemDetail

NewProblemDetail creates a new ProblemDetail with the given status code and detail message. The title is automatically set based on the HTTP status code.

func NewValidationProblemDetail

func NewValidationProblemDetail[T any](detail string, errors []T) *ProblemDetail

NewValidationProblemDetail creates a problem detail for validation errors (HTTP 422). It accepts any slice type for errors, allowing custom validation error structures. The errors are added as an "errors" extension field.

func (*ProblemDetail) MarshalJSON

func (p *ProblemDetail) MarshalJSON() ([]byte, error)

MarshalJSON implements custom JSON marshaling to include extensions as top-level fields

func (*ProblemDetail) Set

func (p *ProblemDetail) Set(key string, value any) *ProblemDetail

Set adds an extension field to the problem detail and returns the ProblemDetail for method chaining. Extension fields are included as top-level JSON properties.

type Renderer

type Renderer interface {
	// JSON writes a JSON response with the given status code and data
	JSON(w http.ResponseWriter, statusCode int, data any) error

	// Text writes a plain text response with the given status code and data
	Text(w http.ResponseWriter, statusCode int, data string) error

	// HTML writes an HTML response with the given status code and data
	HTML(w http.ResponseWriter, statusCode int, data string) error

	// Blob writes a binary response with the given status code, content type, and data
	Blob(w http.ResponseWriter, statusCode int, contentType string, data []byte) error

	// Stream writes a streaming response with the given status code and content type,
	// copying data from the provided reader to the response writer
	Stream(w http.ResponseWriter, statusCode int, contentType string, reader io.Reader) error

	// File serves a file as the response, automatically setting appropriate headers
	File(w http.ResponseWriter, r *http.Request, filename string) error

	// NoContent writes a 204 No Content response with no body
	NoContent(w http.ResponseWriter) error

	// NotModified writes a 304 Not Modified response for conditional requests
	NotModified(w http.ResponseWriter) error

	// Redirect performs an HTTP redirect with the specified status code and location
	Redirect(w http.ResponseWriter, r *http.Request, url string, code int) error

	// ProblemDetail writes an RFC 9457 Problem Details response
	ProblemDetail(w http.ResponseWriter, problem *ProblemDetail) error
}

Renderer handles response rendering for various content types

var Render Renderer = &defaultRenderer{}

Render is the default renderer instance used by the package

type Router

type Router interface {
	// DELETE registers a handler for HTTP DELETE requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	DELETE(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// GET registers a handler for HTTP GET requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	GET(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// HEAD registers a handler for HTTP HEAD requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	HEAD(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// OPTIONS registers a handler for HTTP OPTIONS requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	OPTIONS(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// PATCH registers a handler for HTTP PATCH requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	PATCH(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// POST registers a handler for HTTP POST requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	POST(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// PUT registers a handler for HTTP PUT requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	PUT(path string, h http.Handler, mw ...func(http.Handler) http.Handler)

	// Use adds middleware to the router's global middleware chain.
	// Middleware is applied to all routes registered after this call.
	Use(mw ...func(http.Handler) http.Handler)

	// Group creates a new router scope that inherits the current middleware chain.
	// This allows for organizing routes and applying middleware to specific groups.
	Group(fn func(Router))

	// NotFound sets a custom handler for 404 Not Found responses.
	// If not set, a default handler that returns a problem detail response is used.
	NotFound(h http.Handler)

	// MethodNotAllowed sets a custom handler for 405 Method Not Allowed responses.
	// If not set, a default handler that returns a problem detail response is used.
	MethodNotAllowed(h http.Handler)

	// Files serves static files from embedded FS at the specified prefix.
	// The prefix is stripped from URLs before looking up files in the embedFS.
	Files(prefix string, embedFS embed.FS, dir string)

	// FilesDir serves static files from a directory at the specified prefix.
	// The prefix is stripped from URLs before looking up files in the directory.
	FilesDir(prefix, dir string)

	// Static serves a static web application from embedded FS with configurable fallback behavior.
	// If fallback is true, falls back to index.html for non-existent files (SPA behavior).
	// If fallback is false, uses the custom NotFound handler for missing files.
	// Requests matching apiPrefix patterns return 404 regardless.
	Static(embedFS embed.FS, distDir string, fallback bool, apiPrefix ...string)

	// StaticDir serves a static web application from a directory with configurable fallback behavior.
	// If fallback is true, falls back to index.html for non-existent files (SPA behavior).
	// If fallback is false, uses the custom NotFound handler for missing files.
	// Requests matching apiPrefix patterns return 404 regardless.
	StaticDir(dir string, fallback bool, apiPrefix ...string)

	// ServeMux returns the underlying http.ServeMux for advanced usage or integration.
	ServeMux() *http.ServeMux

	// ServeHTTP implements the http.Handler interface, making the router compatible
	// with Go's standard HTTP server and middleware ecosystem.
	ServeHTTP(w http.ResponseWriter, req *http.Request)

	// Logger returns the logger instance used by the router for logging
	// requests, errors, and other router-specific events.
	Logger() log.Logger

	// SetLogger configures the logger instance that the router should use
	// for logging operations. This allows for custom logger configuration.
	SetLogger(logger log.Logger)

	// Config returns the current configuration used by the router.
	// This configuration controls various aspects of router behavior
	// including middleware settings and error response handling.
	Config() config.Config

	// SetConfig updates the router's configuration. This affects how
	// the router handles various behaviors including middleware settings
	// and error response processing.
	//
	// Note: Changing the configuration affects both regular routes
	// and 404/405 error responses.
	SetConfig(config config.Config)
}

Router interface defines the contract for HTTP routing operations. It provides methods for registering HTTP handlers for specific HTTP methods, applying middleware, creating route groups, and customizing error handlers.

func NewRouter

func NewRouter(mw ...func(http.Handler) http.Handler) Router

NewRouter creates a new router instance with optional global middleware. The middleware provided here will be applied to all routes registered on this router.

Example:

router := NewRouter(loggingMiddleware, authMiddleware)

type Server

type Server struct {
	// Router provides HTTP routing functionality including method-specific
	// route registration, middleware support, and request handling.
	Router
	// contains filtered or unexported fields
}

Server represents a zerohttp server instance that wraps Go's standard HTTP server with additional functionality including middleware support, TLS configuration, automatic certificate management, and structured logging.

The Server embeds a Router interface, providing direct access to HTTP routing methods (GET, POST, PUT, DELETE, etc.) and middleware management.

func New

func New(opts ...config.Option) *Server

New creates and configures a new Server instance with the provided options. It initializes the server with default configurations that can be overridden using the provided options. The server includes HTTP and HTTPS support, middleware integration, and structured logging.

Example usage:

server := zerohttp.New(
    config.WithAddr(":8080"),
    config.WithLogger(myLogger),
)

func (*Server) Close

func (s *Server) Close() error

Close immediately closes all server listeners, terminating any active connections. Unlike Shutdown, this method does not wait for connections to finish gracefully. It closes both HTTP and HTTPS listeners concurrently.

This method is thread-safe and can be called multiple times safely. Returns the last error encountered while closing listeners, or nil if successful.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe starts the HTTP server and begins accepting connections. It creates a listener if one is not already configured and serves HTTP traffic on the configured address. If the server is not configured, this method logs a debug message and returns nil without error.

This method blocks until the server encounters an error or is shut down. Returns any error encountered while starting or running the server.

func (*Server) ListenAndServeTLS

func (s *Server) ListenAndServeTLS(certFile, keyFile string) error

ListenAndServeTLS starts the HTTPS server with the specified certificate files. It creates a TLS listener if one is not already configured and serves HTTPS traffic using the provided certificate and key files. If the TLS server is not configured, this method logs a debug message and returns nil without error.

Parameters:

  • certFile: Path to the TLS certificate file in PEM format
  • keyFile: Path to the TLS private key file in PEM format

This method blocks until the server encounters an error or is shut down. Returns any error encountered while starting or running the TLS server.

func (*Server) ListenerAddr

func (s *Server) ListenerAddr() string

ListenerAddr returns the network address that the HTTP server is listening on. If a listener is configured, it returns the listener's actual address. If no listener is configured but a server is configured, it returns the server's configured address. If neither is configured, it returns an empty string.

This method is thread-safe and can be called concurrently. The returned address includes both host and port (e.g., "127.0.0.1:8080").

func (*Server) ListenerTLSAddr

func (s *Server) ListenerTLSAddr() string

ListenerTLSAddr returns the network address that the HTTPS server is listening on. If a TLS listener is configured, it returns the listener's actual address. If no TLS listener is configured but a TLS server is configured, it returns the server's configured address. If neither is configured, it returns an empty string.

This method is thread-safe and can be called concurrently. The returned address includes both host and port (e.g., "127.0.0.1:8443").

func (*Server) Logger

func (s *Server) Logger() log.Logger

Logger returns the structured logger instance used by the server. This logger is used for recording HTTP requests, errors, server lifecycle events, and can be used by application code for consistent logging.

The returned logger implements the log.Logger interface and provides structured logging capabilities with fields and different log levels.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down both HTTP and HTTPS servers without interrupting any active connections. It waits for active connections to finish or for the provided context to be cancelled.

Parameters:

  • ctx: Context that controls the shutdown timeout. If the context is cancelled before shutdown completes, the servers will be forcefully closed.

The shutdown process runs concurrently for both servers. If any server encounters an error during shutdown, that error is returned. Returns the first error encountered during shutdown, or nil if successful.

func (*Server) Start

func (s *Server) Start() error

Start begins serving both HTTP and HTTPS traffic concurrently. It starts the HTTP server (if configured) and the HTTPS server (if configured with certificates or TLS config). The method returns when the first server encounters an error.

For HTTPS, the server will start if:

  • TLS server is configured AND
  • Either certificates are loaded in TLS config OR certificate files are specified

This method is non-blocking for individual servers but blocks until one fails. Returns the first error encountered by any server during startup or operation.

func (*Server) StartAutoTLS

func (s *Server) StartAutoTLS(hosts ...string) error

StartAutoTLS starts the server with automatic TLS certificate management using Let's Encrypt. It starts both HTTP (for ACME challenges) and HTTPS servers. The HTTP server redirects to HTTPS and handles ACME challenges.

Parameters:

  • hosts: Optional list of hostnames for certificate generation. If not provided, the autocert manager's existing host policy will be used.

The HTTP server handles:

  • ACME challenge requests from Let's Encrypt
  • Redirects all other HTTP traffic to HTTPS

Returns an error if the autocert manager is not configured or if any server fails to start.

func (*Server) StartTLS

func (s *Server) StartTLS(certFile, keyFile string) error

StartTLS is a convenience method that starts only the HTTPS server with the specified certificate files. If the TLS server is not configured, this method returns nil without error.

Parameters:

  • certFile: Path to the TLS certificate file in PEM format
  • keyFile: Path to the TLS private key file in PEM format

This is equivalent to calling ListenAndServeTLS directly. Returns any error encountered while starting or running the TLS server.

type ValidationError

type ValidationError struct {
	// Detail describes what went wrong with the validation
	Detail string `json:"detail"`

	// Pointer is a JSON Pointer (RFC 6901) to the field that failed validation
	Pointer string `json:"pointer,omitempty"`

	// Field is the name of the field that failed validation (alternative to Pointer)
	Field string `json:"field,omitempty"`
}

ValidationError represents a single validation error with optional field location information

Directories

Path Synopsis
examples
autotls command
crud command
file_server command
file_upload command
graceful command
healthcheck command
hello_world command
problem_detail command
spa command
static command
timeout command
tls command
value command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL