zerohttp

package module
v0.93.0 Latest Latest
Warning

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

Go to latest
Published: May 28, 2026 License: MIT Imports: 42 Imported by: 1

README

zerohttp Go Reference Go Report Card Coverage Status

A lightweight, secure-by-default HTTP framework for Go. Built on net/http with zero external dependencies.

Why zerohttp?

Built on stdlib, not instead of it. zerohttp builds on Go's net/http rather than replacing it, so your handlers stay standard http.HandlerFunc and work with existing middleware and tooling.

Secure by default. Sensible security headers, request body limits, panic recovery, and request IDs are applied automatically for every request.

Zero dependencies. Single module, standard library only, so your service stays lean and easy to upgrade.

Handler errors that make sense. Handlers return error, and RFC 9457 Problem Details responses are generated for you automatically.

Features

  • Zero dependencies - Single module, no external deps
  • Secure by default - Security headers, body limits, recovery, request IDs enabled automatically
  • Standard library foundation - Built on net/http, works with any http.Handler middleware
  • Handler errors - Return error, get proper HTTP responses automatically
  • Request binding - JSON, form, multipart, and query params to structs with struct tags
  • Validation - Built-in struct validation with 40+ validators
  • Problem Details - RFC 9457 compliant error responses
  • Middleware - CORS, rate limiting, auth, circuit breaker, and more
  • Metrics - Prometheus-compatible metrics at /metrics
  • Lifecycle hooks - Pre/post startup and shutdown hooks
  • Pluggable - Bring your own validator, tracer, HTTP/3, WebSocket, SSE

Installation

go get github.com/alexferl/zerohttp

Requires Go 1.25 or later.

Quick Start

package main

import (
    "log"
    "net/http"

    zh "github.com/alexferl/zerohttp"
)

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

    app.GET("/hello/{name}", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
        name := zh.Param(r, "name")
        return zh.Render.JSON(w, http.StatusOK, zh.M{"message": "Hello, " + name + "!"})
    }))

    log.Fatal(app.Start())
}
go run main.go
curl http://localhost:8080/hello/world
{"message":"Hello, world!"}

Examples

See the examples/ directory for more complete examples.

Request Binding & Validation
type CreateUserRequest struct {
    Name  string `json:"name" validate:"required,min=2,max=50"`
    Email string `json:"email" validate:"required,email"`
}

app.POST("/users", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    var req CreateUserRequest
    if err := zh.BindAndValidate(r, &req); err != nil {
        return err // Automatic Problem Details response
    }
    // Process valid request...
    return zh.R.JSON(w, http.StatusCreated, req)
}))
Route Groups with Middleware
app.Group(func(api zh.Router) {
    api.Use(basicauth.New(basicauth.Config{
        Credentials: map[string]string{"admin": "secret"},
    }))
    api.GET("/admin/dashboard", dashboardHandler)
})
Query Parameters
type SearchRequest struct {
    Query string `query:"q" validate:"required"`
    Limit int    `query:"limit" validate:"max=100"`
}

app.GET("/search", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    var req SearchRequest
    if err := zh.BindAndValidate(r, &req); err != nil {
        return err
    }
    return zh.R.JSON(w, http.StatusOK, zh.M{"results": []string{}})
}))
Error Handling
app.GET("/users/{id}", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    id := zh.Param(r, "id")
    user, err := db.GetUser(id)
    if err != nil {
        problem := zh.NewProblemDetail(http.StatusNotFound, "user not found")
        return zh.R.ProblemDetail(w, problem)
    }
    return zh.R.JSON(w, http.StatusOK, user)
}))
Response Helpers
app.GET("/health", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    return zh.R.Text(w, http.StatusOK, "healthy")
}))

app.GET("/docs", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    return zh.R.Redirect(w, r, "https://pkg.go.dev/github.com/alexferl/zerohttp", http.StatusFound)
}))

Configuration

zerohttp uses struct-based configuration:

app := zh.New(zh.Config{
    Addr: ":8080",
    TLS: zh.TLSConfig{
        Addr:     ":8443",
        CertFile: "cert.pem",
        KeyFile:  "key.pem",
    },
    RequestBodySize: requestbodysize.Config{
        MaxBytes: 5 * 1024 * 1024, // 5MB
    },
})

Secure by Default

These middlewares are applied automatically:

  • Request ID - Unique IDs for tracing
  • Panic Recovery - Graceful panic handling with stack traces
  • Request Body Size Limits - DoS protection (1MB default)
  • Security Headers - CSP, HSTS, X-Frame-Options, etc.
  • Request Logging - Structured request/response logging

Disable or customize via zh.Config.

Storage

The storage package provides a shared interface for middleware storage backends. Implement storage.Storage once (for Redis, PostgreSQL, etc.) and reuse it across multiple middlewares via adapters:

import (
    "github.com/alexferl/zerohttp/middleware/cache"
    "github.com/alexferl/zerohttp/middleware/idempotency"
    "github.com/alexferl/zerohttp/storage"
)

// Implement storage.Storage and storage.Locker in your own package
type MyRedis struct { /* ... */ }
func (r *MyRedis) Get(ctx context.Context, key string) ([]byte, bool, error) { /* ... */ }
func (r *MyRedis) Set(ctx context.Context, key string, val []byte, ttl time.Duration) error { /* ... */ }
func (r *MyRedis) Delete(ctx context.Context, key string) error { /* ... */ }
func (r *MyRedis) Lock(ctx context.Context, key string, ttl time.Duration) (bool, error) { /* ... */ }
func (r *MyRedis) Unlock(ctx context.Context, key string) error { /* ... */ }

// One backend, multiple middlewares
redis := &MyRedis{}

app.Use(cache.New(cache.Config{
    Store: cache.NewStorageAdapter(redis),
}))

idempotencyStore, _ := idempotency.NewStorageAdapter(redis)
app.Use(idempotency.New(idempotency.Config{
    Store: idempotencyStore,
}))

Testing

The zhtest package provides fluent test helpers:

func TestGetUser(t *testing.T) {
    app := setupRouter()
    req := zhtest.NewRequest(http.MethodGet, "/users/123").Build()
    w := zhtest.Serve(app, req)
    zhtest.AssertWith(t, w).Status(http.StatusOK).JSONPathEqual("name", "John")
}

License

MIT License - see LICENSE for details.

Documentation

Overview

Package zerohttp provides configuration structs for zerohttp servers.

The main Config struct holds server configuration including address, TLS settings, lifecycle hooks, and middleware configuration.

Basic Usage

app := zerohttp.New(zerohttp.Config{
    Addr: ":8080",
})

TLS Configuration

app := zerohttp.New(zerohttp.Config{
    TLS: zerohttp.TLSConfig{
        Addr:     ":8443",
        CertFile: "server.crt",
        KeyFile:  "server.key",
    },
})

Package zerohttp provides a lightweight, zero-dependency HTTP framework for building REST APIs and web applications in Go.

Built on Go's standard library, zerohttp adds essential features for production services: routing, middleware, request binding, validation, rendering, metrics, and more - all without external dependencies.

Quick Start

Create and start a server in a few lines:

package main

import (
    "log"
    "net/http"

    zh "github.com/alexferl/zerohttp"
)

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

    app.GET("/hello/{name}", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
        name := zh.Param(r, "name")
        return zh.Render.JSON(w, http.StatusOK, zh.M{"message": "Hello, " + name})
    }))

    log.Fatal(app.Start())
}

Routing

zerohttp uses Go's standard net/http.ServeMux for routing, supporting path parameters, wildcards, and method-based routes:

app := zh.New()

// Path parameters
app.GET("/users/{id}", getUserHandler)

// Wildcards
app.GET("/files/{path...}", serveFileHandler)

// Route groups with middleware
app.Group(func(api zh.Router) {
    api.Use(basicauth.New(basicauth.Config{
        Credentials: map[string]string{"admin": "secret"},
    }))
    api.GET("/admin/dashboard", dashboardHandler)
})

Handlers

Handlers return errors for cleaner error handling. Errors are automatically converted to appropriate HTTP responses:

app.POST("/users", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    var req CreateUserRequest
    if err := zh.BindAndValidate(r, &req); err != nil {
        return err  // Returns 400 for binding errors, 422 for validation errors
    }

    user, err := createUser(req)
    if err != nil {
        return err  // Returns 500 for unexpected errors
    }

    return zh.Render.JSON(w, http.StatusCreated, user)
}))

Request Binding

Bind request data to structs using Bind:

JSON Binding

Parse JSON request bodies with strict validation (rejects unknown fields):

var req struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}
if err := zh.Bind.JSON(r.Body, &req); err != nil {
    return err  // Returns 400 Bad Request
}

Form Binding

Parse application/x-www-form-urlencoded data:

var form struct {
    Username string   `form:"username"`
    Password string   `form:"password"`
    Remember bool     `form:"remember"`
    Tags     []string `form:"tags"`  // Supports slices
}
if err := zh.Bind.Form(r, &form); err != nil {
    return err
}

Multipart Form Binding

Handle file uploads with multipart/form-data:

var form struct {
    Description string           `form:"description"`
    Document    *zh.FileHeader   `form:"document"`   // Single file
    Images      []*zh.FileHeader `form:"images"`     // Multiple files
}

// maxMemory: bytes to store in memory before temp files
if err := zh.Bind.MultipartForm(r, &form, 32<<20); err != nil {
    return err
}

// Access uploaded files
if form.Document != nil {
    file, err := form.Document.Open()
    if err != nil {
        return err
    }
    defer file.Close()
    data, _ := io.ReadAll(file)
    // Process file data...
}

Query Parameter Binding

Bind query parameters to structs with query tags:

var req struct {
    Query    string   `query:"q"`
    Category string   `query:"category"`
    Tags     []string `query:"tags"`      // Multiple values: ?tags=a&tags=b
    Page     int      `query:"page"`
    Limit    int      `query:"limit"`
    IsActive *bool    `query:"is_active"` // Pointer = optional
}

if err := zh.Bind.Query(r, &req); err != nil {
    return err
}

Embedded Structs

Reuse common patterns like pagination:

type Pagination struct {
    Page  int `query:"page"`
    Limit int `query:"limit"`
}

type ListRequest struct {
    Pagination
    Search string `query:"search"`
}

var req ListRequest
if err := zh.Bind.Query(r, &req); err != nil {
    return err
}

Path Parameters

Type-safe path parameter extraction:

// Basic string extraction
id := zh.Param(r, "id")

// Typed extraction (returns error if invalid)
itemID, err := zh.ParamAs[int](r, "itemID")
if err != nil {
    return zh.NewProblemDetail(http.StatusBadRequest, "Invalid itemID").Render(w)
}

// With default value
category := zh.ParamOrDefault(r, "category", "all")

Individual Parameter Extraction

Extract single query parameters:

// With type conversion
userID, err := zh.QueryParamAs[int](r, "user_id")

// With default value
page := zh.QueryParamAsOrDefault(r, "page", 1)

// Simple string
sort := zh.QueryParam(r, "sort")

Custom Binders

Implement the Binder interface for custom binding logic:

type MyBinder struct{}

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

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

Type Conversion

Form and query binders automatically convert string values to Go types:

string   -> "name=John"           -> "John"
int      -> "age=25"              -> 25
bool     -> "active=true"         -> true
[]string -> "tags=a&tags=b"       -> ["a", "b"]
[]int    -> "ids=1&ids=2"         -> [1, 2]
*string  -> "optional=" or missing -> nil or ""

Supported types: string, all int/uint types, float32, float64, bool, slices, and pointers.

Validation

Validate structs using struct tags with the built-in validator:

type CreateUserRequest struct {
    Name     string `json:"name"     validate:"required,min=2,max=50"`
    Email    string `json:"email"    validate:"required,email"`
    Age      int    `json:"age"      validate:"min=13,max=120"`
    Password string `json:"password" validate:"required,min=8"`
}

if err := zh.Validate.Struct(&req); err != nil {
    // Returns ValidationErrors map keyed by field name
    return err
}

Available Validators

Core validators: required, omitempty, eq, ne

String validators: min, max, len, contains, startswith, endswith, excludes, alpha, alphanum, lowercase, uppercase, ascii, printascii, numeric, oneof

Numeric validators: min, max, gt, lt, gte, lte

Format validators: email, uuid, datetime, base64, hexadecimal, hexcolor, e164, semver, jwt, boolean, json

Network validators: ip, ipv4, ipv6, cidr, hostname, uri, url

Collection validators: unique, each

Combining Validators

Multiple validators can be combined with commas:

type Product struct {
    Name  string   `validate:"required,min=2,max=100"`
    Price float64  `validate:"required,gt=0"`
    Tags  []string `validate:"unique,each,min=2,max=20"`
}

Nested Struct Validation

Validation automatically recurses into nested structs:

type Address struct {
    Street string `validate:"required"`
    City   string `validate:"required"`
}

type Person struct {
    Name    string  `validate:"required"`
    Address Address // validated recursively
}

For slices of structs, use the each validator:

type Order struct {
    Items []LineItem `validate:"each"` // validates each LineItem
}

Pointer Fields

Pointer fields are dereferenced before validation. Use omitempty to make optional:

type User struct {
    Name     *string `validate:"omitempty,min=2"` // nil or valid
    Nickname *string `validate:"required,min=2"`  // must not be nil
}

Custom Validators

Register custom validators with V.Register:

zh.Validate.Register("even", func(value reflect.Value, param string) error {
    if value.Kind() != reflect.Int {
        return fmt.Errorf("even only validates integers")
    }
    if value.Int()%2 != 0 {
        return fmt.Errorf("must be even")
    }
    return nil
})

type Config struct {
    Port int `validate:"required,even"`
}

Validation Error Handling

Validate.Struct returns a ValidationErrors map keyed by field name:

if err := zh.Validate.Struct(&user); err != nil {
    var ve zh.ValidationErrors
    if errors.As(err, &ve) {
        errs := ve.FieldErrors("Email")
        for _, e := range errs {
            fmt.Println(e) // "required" or "must be a valid email"
        }

        if ve.HasErrors() {
            pd := zh.NewValidationProblemDetail("Validation failed", ve)
            pd.Render(w)
        }
    }
}

Errors use JSON field names when available.

Response Rendering

Render responses using Render:

// JSON response
zh.Render.JSON(w, http.StatusOK, zh.M{"users": users})

// Text response
zh.Render.Text(w, http.StatusOK, "Hello, World!")

// HTML response
zh.Render.HTML(w, http.StatusOK, "<h1>Hello</h1>")

// File download
zh.Render.File(w, r, "/path/to/file.pdf")

// Redirect
zh.Render.Redirect(w, r, "/new-path", http.StatusFound)

Error Handling

zerohttp converts errors to RFC 9457 Problem Details responses:

// Return custom problem detail
return zh.NewProblemDetail(http.StatusNotFound, "User not found").Render(w)

// Return validation errors (422 Unprocessable Entity)
return zh.Validate.Struct(&req)

Middleware

Apply middleware at application, group, or route level:

// Application-level
app.Use(cors.New(cors.DefaultConfig))
app.Use(requestid.New())

// Route-level
app.GET("/admin", adminHandler,
    basicauth.New(basicauth.Config{
        Credentials: map[string]string{"admin": "secret"},
    }),
)

Available middleware: cors, basicauth, jwtauth, ratelimit, compress, requestlogger, circuitbreaker, timeout, and more in subpackages. See package middleware for complete documentation.

Metrics

Prometheus-compatible metrics are automatically collected:

// Metrics exposed at /metrics by default
app := zh.New() // No configuration needed

// Access registry in handlers
reg := metrics.GetRegistry(r.Context())
counter := reg.Counter("orders_total", "status")
counter.WithLabelValues("completed").Inc()

See package metrics for detailed metrics documentation.

Pluggable Features

zerohttp provides pluggable interfaces for optional features. Configure via Config:

app := zh.New(zh.Config{
    Validator: myValidator,
    Tracer:    myTracer,
    Extensions: zh.ExtensionsConfig{
        AutocertManager:    myCertManager,
        HTTP3Server:        myH3Server,
        SSEProvider:        mySSEProvider,
        WebSocketUpgrader:  myWSUpgrader,
        WebTransportServer: myWTServer,
    },
})

Custom Validator

Bring your own struct validator (e.g., go-playground/validator/v10):

type myValidator struct {
    v *validator.Validate
}

func (m *myValidator) Struct(dst any) error {
    return m.v.Struct(dst)
}

func (m *myValidator) Register(name string, fn func(reflect.Value, string) error) {
    m.v.RegisterValidation(name, func(fl validator.FieldLevel) bool {
        err := fn(fl.Field(), fl.Param())
        return err == nil
    })
}

app := zh.New(zh.Config{Validator: &myValidator{v: validator.New()}})

Distributed Tracing

Integrate your preferred tracing solution:

app := zh.New(zh.Config{Tracer: myTracer})
app.Use(tracer.New(myTracer))

// In handlers
span := trace.SpanFromContext(r.Context())
span.SetAttributes(trace.String("user.id", userID))

See package trace for interface details.

Auto-TLS

Automatic certificate management via Let's Encrypt:

manager := &autocert.Manager{
    Cache:      autocert.DirCache("/tmp/certs"),
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example.com"),
}

app := zh.New(zh.Config{
    Extensions: zh.ExtensionsConfig{
        AutocertManager: manager,
    },
})
app.StartAutoTLS()

HTTP/3

HTTP/3 support over QUIC:

h3Server := &http3.Server{
    Addr:    ":443",
    Handler: app,
}

app.SetHTTP3Server(h3Server)
app.StartTLS("cert.pem", "key.pem") // HTTP/3 starts automatically

Server-Sent Events

Real-time unidirectional streaming:

app := zh.New(zh.Config{
    Extensions: zh.ExtensionsConfig{
        SSEProvider: sse.NewDefaultProvider(),
    },
})

app.GET("/events", func(w http.ResponseWriter, r *http.Request) error {
    provider := app.SSEProvider()
    stream, err := provider.New(w, r)
    if err != nil {
        return err
    }
    defer stream.Close()

    for i := 0; i < 10; i++ {
        stream.Send(sse.Event{Name: "message", Data: []byte("hello")})
        time.Sleep(1 * time.Second)
    }
    return nil
})

WebSocket

Real-time bidirectional communication. Bring your own library:

app := zh.New(zh.Config{
    Extensions: zh.ExtensionsConfig{
        WebSocketUpgrader: &myUpgrader{upgrader: websocketUpgrader},
    },
})

app.GET("/ws", func(w http.ResponseWriter, r *http.Request) error {
    ws, err := app.WebSocketUpgrader().Upgrade(w, r)
    if err != nil {
        return err
    }
    defer ws.Close()
    // Handle connection...
    return nil
})

WebTransport

Low-latency bidirectional communication over HTTP/3:

h3 := &http3.Server{Addr: ":8443", Handler: app}
wtServer := &webtransport.Server{H3: h3, CheckOrigin: func(r *http.Request) bool { return true }}
webtransport.ConfigureHTTP3Server(h3)

app.SetWebTransportServer(wtServer)
app.ListenAndServeTLS("cert.pem", "key.pem")

Configuration

Configure the server using Config:

app := zh.New(zh.Config{
    Server: &http.Server{
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   15 * time.Second,
        MaxHeaderBytes: 1 << 20,
    },
})

Server Lifecycle

Start the server with various methods:

// HTTP
app.Start()              // Uses config.Addr or :8080
app.ListenAndServe()     // Uses configured address

// HTTPS
app.StartTLS("cert.pem", "key.pem")
app.StartAutoTLS()       // Let's Encrypt

// With graceful shutdown
go app.Start()

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := app.Shutdown(ctx); err != nil {
    log.Fatal(err)
}

Testing

The zhtest package provides fluent test helpers:

func TestGetUser(t *testing.T) {
    app := setupRouter()

    req := zhtest.NewRequest(http.MethodGet, "/users/123").Build()
    w := zhtest.Serve(app, req)

    zhtest.AssertWith(t, w).
        Status(http.StatusOK).
        Header("Content-Type", "application/json").
        JSONPathEqual("name", "John Doe")
}

See package zhtest for detailed testing documentation.

Short Aliases

For convenience, common types have short aliases:

zh.M      // map[string]any - for JSON responses
zh.B      // Bind (alias for Bind)
zh.R      // Render (alias for Render)
zh.V      // Validate (alias for Validate)

Package zerohttp provides path parameter extraction with type conversion.

Path parameters are extracted from URL patterns like /users/{id}:

app.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) error {
    // String value
    id := zh.Param(r, "id")

    // Typed extraction
    userID, err := zh.ParamAs[int](r, "id")
    if err != nil {
        return zh.NewProblemDetail(http.StatusBadRequest, "Invalid ID")
    }

    // With default
    category := zh.ParamOrDefault(r, "category", "all")

    // ...
})

Supported types: string, all int/uint types, float32, float64, bool.

Package zerohttp provides query parameter extraction with type conversion.

The Query extractor provides type-safe access to URL query parameters:

// String value
q := zh.QueryParam(r, "search")

// Typed extraction
page, err := zh.QueryParamAs[int](r, "page")
if err != nil {
    return err
}

// With default
limit := zh.QueryParamAsOrDefault(r, "limit", 20)

// Struct binding
var req struct {
    Search string `query:"search"`
    Page   int    `query:"page"`
}
if err := zh.Bind.Query(r, &req); err != nil {
    return err
}

Package zerohttp provides HTTP/3 server support. See Server.ListenAndServeHTTP3.

Package zerohttp provides server lifecycle hooks. See Server.RegisterPreStartupHook, Server.RegisterStartupHook, and Server.RegisterShutdownHook.

Package zerohttp provides metrics server support. See Server.Metrics and Server.MetricsAddr.

Package zerohttp provides SSE provider configuration. See Server.SetSSEProvider and Server.SSEProvider.

Package zerohttp provides TLS and HTTPS server support. See Server.ListenAndServeTLS and Server.StartAutoTLS.

Package zerohttp provides WebSocket upgrader configuration. See Server.SetWebSocketUpgrader and Server.WebSocketUpgrader.

Package zerohttp provides WebTransport server support. See Server.SetWebTransportServer.

Package zerohttp provides HTML template rendering support using Go's standard html/template package.

The TemplateManager simplifies template rendering from embedded filesystems:

//go:embed templates/*
var templatesFS embed.FS

tmpl := zh.NewTemplateManager(templatesFS, "templates/*.html")

app.GET("/", func(w http.ResponseWriter, r *http.Request) error {
    return tmpl.Render(w, http.StatusOK, "index.html", zh.M{"title": "Home"})
})

Index

Constants

View Source
const (
	DefaultReadTimeout       = 10 * time.Second
	DefaultReadHeaderTimeout = 5 * time.Second
	DefaultWriteTimeout      = 15 * time.Second
	// DefaultIdleTimeout is explicitly set to 60s.
	// Note: If IdleTimeout is 0, Go falls back to ReadTimeout. We set this
	// explicitly to avoid confusion and ensure the intended timeout is used.
	DefaultIdleTimeout    = 60 * time.Second
	DefaultMaxHeaderBytes = 16 * 1024 // 16 KB -> actual limit is ~20 KB (Go adds +4096)
)

Variables

View Source
var B = Bind

B is a short alias for Bind.

if err := zh.B.JSON(r.Body, &req); err != nil {
    return err
}
View Source
var DefaultConfig = Config{
	Addr: "localhost:8080",
	TLS: TLSConfig{
		Addr:         "localhost:8443",
		Server:       nil,
		RedirectHTTP: true,
	},
	DisableDefaultMiddlewares: false,
	DefaultMiddlewares:        nil,
	Recover:                   recover.DefaultConfig,
	RequestBodySize:           requestbodysize.DefaultConfig,
	RequestID:                 requestid.DefaultConfig,
	RequestLogger:             requestlogger.DefaultConfig,
	SecurityHeaders:           securityheaders.DefaultConfig,
	Metrics:                   metrics.DefaultConfig,
	Logger:                    nil,
	Server:                    nil,
}

DefaultConfig contains all default values used by Config. Update this file if you want to change system-wide defaults.

View Source
var DefaultMultipartMaxMemory int64 = 32 << 20

DefaultMultipartMaxMemory is the default max memory for multipart form parsing in BindAndValidate. This can be changed globally. Default is 32MB.

View Source
var Params = &defaultParamsExtractor{}

Params is the default params extractor instance used by the package

View Source
var Query = &defaultQueryExtractor{}

Query is the default query extractor instance used by the package

R is a short alias for Render.

return zh.R.JSON(w, http.StatusOK, data)

V is a short alias for Validate.

if err := zh.V.Struct(&req); err != nil {
    return err
}
View Source
var Validate = validator.New()

Validate is the default Validator instance used by the package. Use it to validate structs using struct tags:

type CreateUserRequest struct {
    Name  string `json:"name" validate:"required,min=2"`
    Email string `json:"email" validate:"required,email"`
}

if err := zh.Validate.Struct(&req); err != nil {
    // Returns 422 Unprocessable Entity with field errors
    return err
}

For convenience, use the V alias or BindAndValidate for combined binding and validation.

Functions

func BindAndValidate added in v0.18.0

func BindAndValidate(r *http.Request, dst any) error

BindAndValidate binds request data based on Content-Type and validates the result. It returns appropriate errors:

  • 400 Bad Request for binding failures (malformed JSON, type mismatches)
  • 422 Unprocessable Entity for unknown fields or validation failures

Supported Content-Types:

  • application/json
  • application/x-www-form-urlencoded
  • multipart/form-data
  • (no content-type) - parses query parameters

Example:

func handler(w http.ResponseWriter, r *http.Request) error {
    var req CreateUserRequest
    if err := zh.BindAndValidate(r, &req); err != nil {
        return err  // 400 or 422 auto-detected
    }
    // ...
}

func BindMultipartFormFiles added in v0.9.0

func BindMultipartFormFiles(r *http.Request, dst any) error

BindMultipartFormFiles binds file uploads to struct fields after the main form binding. This is called internally by handler logic when processing multipart forms. Exported for advanced use cases.

func DefaultHTTPServer added in v0.74.0

func DefaultHTTPServer() *http.Server

DefaultHTTPServer returns a new http.Server with sensible defaults. Use this as a base when you need to customize only specific server fields while keeping the other defaults.

Example:

srv := zerohttp.DefaultHTTPServer()
srv.ReadTimeout = 30 * time.Second
app := zerohttp.New(zerohttp.Config{
    Server: srv,
})

func DefaultTLSServer added in v0.74.0

func DefaultTLSServer() *http.Server

DefaultTLSServer returns a new http.Server with sensible defaults for TLS. Use this as a base when you need to customize only specific TLS server fields while keeping the other defaults.

Example:

srv := zerohttp.DefaultTLSServer()
srv.ReadTimeout = 30 * time.Second
app := zerohttp.New(zerohttp.Config{
    TLS: TLSConfig{
        Server: srv,
    },
})

func IsBindError added in v0.25.0

func IsBindError(err error) bool

IsBindError checks if an error is a binding error (should return 400).

func IsUnknownFieldError added in v0.90.0

func IsUnknownFieldError(err error) bool

IsUnknownFieldError checks if an error is an unknown field error (should return 422).

func IsValidationError added in v0.18.0

func IsValidationError(err error) bool

IsValidationError checks if an error is a validation error (should return 422).

func Param added in v0.10.0

func Param(r *http.Request, name string) string

Param is a convenience function that calls Params.Param

func ParamAs added in v0.10.0

func ParamAs[T ParamType](r *http.Request, name string) (T, error)

ParamAs extracts and converts a path parameter to type T. Returns an error if the parameter is missing or conversion fails. Supported types: string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool

func ParamAsOrDefault added in v0.10.0

func ParamAsOrDefault[T ParamType](r *http.Request, name string, defaultVal T) T

ParamAsOrDefault extracts and converts a path parameter to type T, returning a default value if the parameter is missing or conversion fails.

func ParamOrDefault added in v0.10.0

func ParamOrDefault(r *http.Request, name, defaultVal string) string

ParamOrDefault is a convenience function that calls Params.ParamOrDefault

func QueryParam added in v0.11.0

func QueryParam(r *http.Request, name string) string

QueryParam is a convenience function that calls Query.QueryParam

func QueryParamAs added in v0.11.0

func QueryParamAs[T ParamType](r *http.Request, name string) (T, error)

QueryParamAs extracts and converts a query parameter to type T. Returns an error if conversion fails. For missing parameters, returns the zero value and no error (use pointer types for optional params). Supported types: string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool

func QueryParamAsOrDefault added in v0.11.0

func QueryParamAsOrDefault[T ParamType](r *http.Request, name string, defaultVal T) T

QueryParamAsOrDefault extracts and converts a query parameter to type T, returning a default value if the parameter is missing or conversion fails.

func QueryParamOrDefault added in v0.11.0

func QueryParamOrDefault(r *http.Request, name, defaultVal string) string

QueryParamOrDefault is a convenience function that calls Query.QueryParamOrDefault

func RenderAndValidate added in v0.18.0

func RenderAndValidate(w http.ResponseWriter, status int, data any) error

RenderAndValidate renders JSON response after validating the data. This catches server-side bugs like missing required fields before sending responses.

If validation fails, it returns a 500 Internal Server Error (server bug).

Example:

func handler(w http.ResponseWriter, r *http.Request) error {
    user := User{ID: "...", Name: "John"}
    return zh.RenderAndValidate(w, http.StatusOK, user)
}

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

	// Form parses form data from the request body (application/x-www-form-urlencoded)
	// and binds it to the destination struct using `form` tags.
	// It also parses the query string and includes those values.
	Form(r *http.Request, dst any) error

	// MultipartForm parses multipart/form-data from the request,
	// including file uploads, and binds values to the destination struct.
	// The maxMemory parameter controls how much of the form data is stored in memory
	// before being written to temp files (similar to http.Request.ParseMultipartForm).
	// File uploads are bound to fields of type FileHeader or []FileHeader.
	MultipartForm(r *http.Request, dst any, maxMemory int64) error

	// Query binds query parameters from the request URL to a destination struct.
	// Uses `query` struct tags for field mapping. Fields without tags are mapped
	// using snake_case conversion of the field name.
	// Returns an error if binding fails due to type mismatch.
	Query(r *http.Request, 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. Use it to decode request bodies into structs:

var req CreateUserRequest
if err := zh.Bind.JSON(r.Body, &req); err != nil {
    return err
}

For convenience, use the B alias.

type Config added in v0.58.0

type Config struct {
	// Addr is the address for the HTTP server to listen on.
	// Default: "localhost:8080"
	Addr string

	// Server is the HTTP server instance for plain (non-TLS) traffic.
	// Default: preconfigured server listening on "localhost:8080"
	Server *http.Server

	// Listener allows specifying a custom net.Listener for HTTP traffic (optional).
	// Default: nil (system default listener will be created)
	Listener net.Listener

	// TLS holds the configuration for the HTTPS server.
	TLS TLSConfig

	// Lifecycle holds the server startup and shutdown hook configuration.
	Lifecycle LifecycleConfig

	// Logger is the logger instance used by the server and middlewares.
	// Default: nil (a default logger will be created if nil)
	Logger log.Logger

	// DisableDefaultMiddlewares disables all built-in default middlewares when true.
	// Default: false (default middlewares are enabled)
	DisableDefaultMiddlewares bool

	// DefaultMiddlewares is a custom list of middlewares to use. If nil, uses the built-in default middleware list.
	// Default: nil (means use built-in defaults)
	DefaultMiddlewares []MiddlewareFunc

	// Recover holds the configuration for the panic recovery middleware.
	Recover recover.Config

	// RequestBodySize holds the configuration for the request body size limiting middleware.
	RequestBodySize requestbodysize.Config

	// RequestID holds the configuration for the request ID generation middleware.
	RequestID requestid.Config

	// RequestLogger holds the configuration for the HTTP request logging middleware.
	RequestLogger requestlogger.Config

	// SecurityHeaders holds the configuration for the security headers middleware.
	SecurityHeaders securityheaders.Config

	// Metrics holds the configuration for the metrics middleware.
	Metrics metrics.Config

	// Tracer holds the configuration for the tracing middleware.
	// Default: DefaultTracerConfig
	Tracer tracer.Config

	// Validator is an optional struct validator for validating request data.
	// Users can inject their own implementation (e.g., github.com/go-playground/validator/v10).
	// The validator must implement the Validator interface.
	// If nil, the default built-in validator will be used.
	// Default: nil
	Validator Validator

	// Extensions holds optional protocol and feature extensions.
	Extensions ExtensionsConfig
}

Config holds server and middleware configuration options for zerohttp.

type ExtensionsConfig added in v0.58.0

type ExtensionsConfig struct {
	// AutocertManager is an optional autocert manager for automatic certificate management (AutoTLS).
	// Users can inject their own implementation (e.g., golang.org/x/crypto/acme/autocert.Manager)
	// by implementing the AutocertManager interface.
	// Default: nil (AutoTLS not enabled unless set)
	AutocertManager autocert.Manager

	// HTTP3Server is an optional HTTP/3 server instance for handling HTTP/3 traffic over QUIC.
	// Users can inject their own HTTP/3 implementation (e.g., quic-go/http3).
	// The server must implement the HTTP3Server interface.
	// Default: nil (HTTP/3 not enabled unless set)
	HTTP3Server http3.Server

	// SSEProvider is an optional handler for Server-Sent Events connections.
	// Users can set their own provider (e.g., wrapping a custom SSE library).
	// If nil, SSE is not available but users can still handle SSE manually in their handlers.
	// Default: nil
	SSEProvider sse.Provider

	// WebSocketUpgrader is an optional handler for WebSocket upgrades.
	// Users can set their own upgrader (e.g., wrapping gorilla/websocket).
	// If nil, WebSocket is not available but users can still handle
	// upgrades manually in their handlers.
	WebSocketUpgrader websocket.Upgrader

	// WebTransportServer is an optional WebTransport server for handling WebTransport sessions.
	// Users can inject their own implementation (e.g., quic-go/webtransport-go).
	// The server must implement the WebTransportServer interface.
	// If nil, WebTransport support will not be enabled.
	// The server will be started automatically when ListenAndServeTLS or Start is called.
	// Default: nil
	WebTransportServer webtransport.Server
}

type FileHeader added in v0.9.0

type FileHeader = bind.FileHeader

FileHeader represents an uploaded file in a multipart form. It provides access to the file's metadata and content. This is an alias to internal/bind.FileHeader.

type HandlerFunc

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

HandlerFunc is a handler function that returns an error. It implements http.Handler, allowing it to be used anywhere a standard HTTP handler is expected.

Errors are automatically converted to appropriate HTTP responses:

  • Validation errors return 422 Unprocessable Entity with field details
  • Unknown fields return 422 Unprocessable Entity
  • Binding errors return 400 Bad Request
  • Request too large returns 413 Payload Too Large
  • ProblemDetail errors return their specified status code
  • All other errors return 500 Internal Server Error

Example:

app.GET("/users/{id}", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    id := zh.Param(r, "id")
    user, err := db.GetUser(id)
    if err != nil {
        return err // Returns 500
    }
    if user == nil {
        return zh.NewProblemDetail(http.StatusNotFound, "User not found").Render(w)
    }
    return zh.Render.JSON(w, http.StatusOK, user)
}))

func (HandlerFunc) ServeHTTP

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

ServeHTTP implements http.Handler interface. It handles all errors directly; no panic propagation is used.

type LifecycleConfig added in v0.58.0

type LifecycleConfig struct {
	// PreStartupHooks are hooks that execute sequentially before any startup hooks.
	// These run before the server begins initialization.
	// Default: nil
	PreStartupHooks []StartupHookConfig

	// StartupHooks are hooks that execute sequentially before the server starts
	// accepting connections. If any startup hook returns an error, the server
	// will not start.
	// Default: nil
	StartupHooks []StartupHookConfig

	// PostStartupHooks are hooks that execute sequentially after the server has
	// started accepting connections.
	// Default: nil
	PostStartupHooks []StartupHookConfig

	// PreShutdownHooks are hooks that execute sequentially before server shutdown begins.
	// These run before any servers start shutting down.
	// Default: nil
	PreShutdownHooks []ShutdownHookConfig

	// ShutdownHooks are hooks that execute concurrently with server shutdown.
	// These run alongside the HTTP/HTTPS/HTTP3 server shutdown.
	// Default: nil
	ShutdownHooks []ShutdownHookConfig

	// PostShutdownHooks are hooks that execute sequentially after all servers are shut down.
	// These run after all servers have completed shutdown.
	// Default: nil
	PostShutdownHooks []ShutdownHookConfig
}

type M

type M map[string]any

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

return zh.Render.JSON(w, http.StatusOK, zh.M{
    "message": "Hello, World!",
    "count":   42,
})

type MiddlewareFunc added in v0.79.0

type MiddlewareFunc func(http.Handler) http.Handler

HandlerFunc is a handler function that returns an error. It implements http.Handler, allowing it to be used anywhere a standard HTTP handler is expected.

Errors are automatically converted to appropriate HTTP responses:

  • Validation errors return 422 Unprocessable Entity with field details
  • Unknown fields return 422 Unprocessable Entity
  • Binding errors return 400 Bad Request
  • Request too large returns 413 Payload Too Large
  • ProblemDetail errors return their specified status code
  • All other errors return 500 Internal Server Error

Example:

app.GET("/users/{id}", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    id := zh.Param(r, "id")
    user, err := db.GetUser(id)
    if err != nil {
        return err // Returns 500
    }
    if user == nil {
        return zh.NewProblemDetail(http.StatusNotFound, "User not found").Render(w)
    }
    return zh.Render.JSON(w, http.StatusOK, user)
}))

func DefaultMiddlewares added in v0.58.0

func DefaultMiddlewares(cfg Config, logger log.Logger) []MiddlewareFunc

DefaultMiddlewares returns the default set of middlewares with the provided configuration. The returned middlewares are applied in the following order:

  • RequestID: Assigns a unique request ID to each request
  • Recover: Recovers from panics and logs errors
  • RequestBodySize: Limits the maximum request body size
  • SecurityHeaders: Adds security-related HTTP headers
  • RequestLogger: Logs HTTP requests and responses

type ParamExtractor added in v0.10.0

type ParamExtractor interface {
	// Param gets a path parameter by name as a string.
	// Returns empty string if parameter is not found.
	Param(r *http.Request, name string) string

	// ParamOrDefault gets a path parameter with a fallback default value.
	ParamOrDefault(r *http.Request, name, defaultVal string) string
}

ParamExtractor defines the interface for path parameter extraction

type ParamType added in v0.10.0

type ParamType interface {
	~string | ~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~float32 | ~float64 |
		~bool
}

ParamType is a type constraint for supported path parameter types. Supported types: string, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool

type ProblemDetail

type ProblemDetail = problem.Detail

ProblemDetail is an alias to problem.Detail. It represents an RFC 9457 Problem Details response, a standardized format for returning error details from HTTP APIs.

Example usage:

return zh.NewProblemDetail(http.StatusNotFound, "User not found").Render(w)

Or return validation errors:

return zh.Validate.Struct(&req) // Returns 422 with field errors

func NewProblemDetail

func NewProblemDetail(statusCode int, detail string) *ProblemDetail

NewProblemDetail creates a new ProblemDetail with the given status code and detail message. This is a convenience wrapper around problem.NewDetail.

func NewValidationProblemDetail

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

NewValidationProblemDetail creates a problem detail for validation errors (HTTP 422). This is a convenience wrapper around problem.NewValidationDetail.

type QueryExtractor added in v0.11.0

type QueryExtractor interface {
	// QueryParam gets a query parameter by name as a string.
	// Returns empty string if parameter is not found.
	QueryParam(r *http.Request, name string) string

	// QueryParamOrDefault gets a query parameter with a fallback default value.
	QueryParamOrDefault(r *http.Request, name, defaultVal string) string
}

QueryExtractor defines the interface for query parameter extraction

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

	// Template renders an HTML template with proper Content-Type header
	Template(w http.ResponseWriter, code int, tmpl *template.Template, name string, data any) 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. Use it to write HTTP responses in various formats:

// JSON response
zh.Render.JSON(w, http.StatusOK, user)

// Plain text
zh.Render.Text(w, http.StatusOK, "Hello")

// File download
zh.Render.File(w, r, "/path/to/document.pdf")

For convenience, use the R alias.

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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// 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 ...MiddlewareFunc)

	// CONNECT registers a handler for HTTP CONNECT requests to the specified path.
	// Additional middleware can be provided that will be applied only to this route.
	// CONNECT is typically used for WebSocket and WebTransport upgrades.
	CONNECT(path string, h http.Handler, mw ...MiddlewareFunc)

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

	// 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

	// 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)
}

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 ...MiddlewareFunc) 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(cfg ...Config) *Server

New creates and configures a new Server instance with the provided It initializes the server with sensible defaults that can be overridden using the provided

The server includes:

  • HTTP and HTTPS support
  • Middleware integration
  • Structured logging
  • Automatic metrics collection (enabled by default)
  • Request binding and validation

Example - Basic usage with defaults:

app := zh.New()
app.GET("/", handler)
log.Fatal(app.Start())

Example - With custom configuration:

app := zh.New(Config{
    Addr: ":8080",
    Server: &http.Server{
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 15 * time.Second,
    },
    Logger: myLogger,
    Metrics: metrics.Config{
        Enabled: config.Bool(false), // Disable metrics
    },
})

Example - Customizing only specific server fields (keeps other defaults):

srv := zh.DefaultHTTPServer()
srv.ReadTimeout = 30 * time.Second
app := zh.New(Config{
    Addr:   ":8080",
    Server: srv,
})

Example - With pluggable validator:

app := zh.New(Config{
    Validator: myCustomValidator,
})

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) ListenAndServeHTTP3 added in v0.6.0

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

ListenAndServeHTTP3 starts the HTTP/3 server with the specified certificate files. HTTP/3 requires TLS and uses the provided certificate and key files for encryption. If the HTTP/3 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 HTTP/3 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) Metrics added in v0.23.0

func (s *Server) Metrics() metrics.Registry

Metrics returns the metrics registry for collecting custom metrics. Returns nil if metrics are not enabled.

Use this to create custom metrics in your handlers or middleware:

requests := app.Metrics().Counter("my_requests_total", "status")
requests.WithLabelValues("200").Inc()

func (*Server) MetricsAddr added in v0.24.0

func (s *Server) MetricsAddr() string

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

This method is thread-safe and can be called concurrently.

func (*Server) RegisterPostShutdownHook added in v0.14.0

func (s *Server) RegisterPostShutdownHook(name string, hook ShutdownHook)

RegisterPostShutdownHook registers a hook to run after servers have shut down. Post-shutdown hooks execute sequentially in registration order. Errors from post-shutdown hooks are logged but do not affect shutdown.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, shutdown will hang.

Example:

app.RegisterPostShutdownHook("cleanup", func(ctx context.Context) error {
    return os.RemoveAll("/tmp/app-*")
})

func (*Server) RegisterPostStartupHook added in v0.36.0

func (s *Server) RegisterPostStartupHook(name string, hook StartupHook)

RegisterPostStartupHook registers a hook to run after servers have started accepting connections. Post-startup hooks execute sequentially in registration order. Errors from post-startup hooks are logged but do not stop the server.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, startup will hang.

Example:

app.RegisterPostStartupHook("announce-ready", func(ctx context.Context) error {
    return notifyServiceDiscovery()
})

func (*Server) RegisterPreShutdownHook added in v0.14.0

func (s *Server) RegisterPreShutdownHook(name string, hook ShutdownHook)

RegisterPreShutdownHook registers a hook to run before server shutdown begins. Pre-shutdown hooks execute sequentially in registration order, before servers stop. Errors from pre-shutdown hooks are logged but do not stop shutdown.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, shutdown will hang.

Example:

app.RegisterPreShutdownHook("health", func(ctx context.Context) error {
    health.SetUnhealthy()
    return nil
})

func (*Server) RegisterPreStartupHook added in v0.36.0

func (s *Server) RegisterPreStartupHook(name string, hook StartupHook)

RegisterPreStartupHook registers a hook to run before servers start and before startup hooks. Pre-startup hooks execute sequentially in registration order. If any pre-startup hook returns an error, the server will not start.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, startup will hang.

Example:

app.RegisterPreStartupHook("validate-config", func(ctx context.Context) error {
    return validateConfig()
})

func (*Server) RegisterShutdownHook added in v0.14.0

func (s *Server) RegisterShutdownHook(name string, hook ShutdownHook)

RegisterShutdownHook registers a hook to run concurrently with server shutdown. Shutdown hooks execute concurrently alongside server shutdown. Errors from shutdown hooks are logged but do not stop shutdown.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, shutdown will hang.

Example:

app.RegisterShutdownHook("close-db", func(ctx context.Context) error {
    return db.Close()
})

func (*Server) RegisterStartupHook added in v0.35.0

func (s *Server) RegisterStartupHook(name string, hook StartupHook)

RegisterStartupHook registers a hook to run concurrently with servers starting up. Startup hooks execute sequentially in registration order, after PreStartupHooks. If any startup hook returns an error, the server will not start.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, startup will hang.

Example:

app.RegisterStartupHook("migrations", func(ctx context.Context) error {
    return goose.Up(db.DB, "migrations")
})

func (*Server) SSEProvider added in v0.16.0

func (s *Server) SSEProvider() sse.Provider

SSEProvider returns the configured SSE provider (if any). Returns nil if no SSE provider has been configured.

func (*Server) SetHTTP3Server added in v0.6.0

func (s *Server) SetHTTP3Server(server http3.Server)

SetHTTP3Server sets the HTTP/3 server instance. This can be used to inject an HTTP/3 implementation (e.g., quic-go/http3) after creating the server.

The HTTP/3 server will be started automatically when ListenAndServeTLS or StartTLS is called. You don't need to call ListenAndServeTLS on the HTTP/3 server yourself.

Parameters:

  • server: An HTTP/3 server instance implementing the HTTP3Server interface

func (*Server) SetSSEProvider added in v0.16.0

func (s *Server) SetSSEProvider(provider sse.Provider)

SetSSEProvider sets the SSE provider instance. This can be used to inject an SSE implementation after creating the server.

Users can implement their own SSE provider or use the built-in stdlib provider:

app := zerohttp.New()
app.SetSSEProvider(zh.NewDefaultProvider())

app.GET("/events", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    provider := app.SSEProvider()
    sse, err := provider.New(w, r)
    if err != nil {
        return err
    }
    defer sse.Close()
    // ... stream events ...
}))

Parameters:

  • provider: An SSE provider instance implementing the SSEProvider interface

func (*Server) SetValidator added in v0.18.0

func (s *Server) SetValidator(validator Validator)

SetValidator sets the struct validator instance. This can be used to inject a custom validation implementation (e.g., go-playground/validator/v10) after creating the server. If nil, the default built-in validator will be used.

Example:

import "github.com/go-playground/validator/v10"

app := zerohttp.New()
app.SetValidator(&myValidator{v: validator.New()})

Parameters:

  • validator: A validator instance implementing the Validator interface

func (*Server) SetWebSocketUpgrader added in v0.15.0

func (s *Server) SetWebSocketUpgrader(upgrader websocket.Upgrader)

SetWebSocketUpgrader sets the WebSocket upgrader instance. This can be used to inject a WebSocket implementation (e.g., gorilla/websocket, nhooyr/websocket) after creating the server.

The WebSocket upgrader provides the Upgrade method for handling WebSocket connections. Users bring their own WebSocket library and implement the WebSocketUpgrader interface, or use a thin wrapper around their preferred library.

Example with gorilla/websocket:

import "github.com/gorilla/websocket"

upgrader := &websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}

app := zerohttp.New()
app.SetWebSocketUpgrader(&myUpgrader{upgrader})

app.GET("/ws", zh.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
    ws, err := app.WebSocketUpgrader().Upgrade(w, r)
    if err != nil {
        return err
    }
    defer ws.Close()
    // ... handle connection ...
}))

Parameters:

  • upgrader: A WebSocket upgrader instance implementing the config.WebSocketUpgrader interface

func (*Server) SetWebTransportServer added in v0.7.0

func (s *Server) SetWebTransportServer(server webtransport.Server)

SetWebTransportServer sets the WebTransport server instance. This can be used to inject a WebTransport implementation (e.g., quic-go/webtransport-go) after creating the server.

The WebTransport server will be started automatically when ListenAndServeTLS or Start is called. You don't need to call ListenAndServeTLS on the WebTransport server yourself.

Parameters:

  • server: A WebTransport server instance implementing the config.WebTransportServer interface

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. Shutdown hooks are executed during the shutdown process:

  • Pre-shutdown hooks run sequentially before server shutdown begins
  • Shutdown hooks run concurrently with server shutdown
  • Post-shutdown hooks run sequentially after all servers are shut down

Returns the first error encountered during shutdown, or nil if successful.

func (*Server) Start

func (s *Server) Start() error

Start begins serving HTTP, HTTPS, and metrics traffic concurrently. It starts all configured servers (HTTP, HTTPS, metrics, HTTP/3, WebTransport) in separate goroutines and blocks until all servers exit.

For HTTPS, the server will start if:

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

Start blocks until all servers exit. If any server encounters an unexpected error (i.e. not ErrServerClosed), that error is returned immediately. Returns nil when all servers shut down cleanly (e.g. via Shutdown()).

func (*Server) StartAutoTLS

func (s *Server) StartAutoTLS() 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.

Users must configure the AutocertManager with their desired host policy before calling this method. For example, using golang.org/x/crypto/acme/autocert:

mgr := &autocert.Manager{
    Cache:      autocert.DirCache("/var/cache/certs"),
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example.com"),
}
srv := zerohttp.New(WithAutocertManager(mgr))
srv.StartAutoTLS()

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) StartHTTP3 added in v0.6.0

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

StartHTTP3 starts only the HTTP/3 server with the specified certificate files. This is a convenience method for starting just HTTP/3 without HTTP or HTTPS. If the HTTP/3 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 ListenAndServeHTTP3 directly. Returns any error encountered while starting or running the HTTP/3 server.

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.

func (*Server) Validator added in v0.18.0

func (s *Server) Validator() Validator

Validator returns the configured struct validator (if any). Returns nil if no custom validator has been configured - in this case, the default built-in validator (zh.V) should be used.

func (*Server) WebSocketUpgrader added in v0.15.0

func (s *Server) WebSocketUpgrader() websocket.Upgrader

WebSocketUpgrader returns the configured WebSocket upgrader (if any). Returns nil if no WebSocket upgrader has been configured.

type ShutdownHook added in v0.58.0

type ShutdownHook func(ctx context.Context) error

ShutdownHook is a function called during server shutdown. The context passed to the hook will be cancelled when the shutdown timeout is reached or if the parent context is cancelled.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, shutdown will hang.

type ShutdownHookConfig added in v0.58.0

type ShutdownHookConfig struct {
	Name string
	Hook ShutdownHook
}

ShutdownHookConfig configures a shutdown hook.

type StartupHook added in v0.58.0

type StartupHook func(ctx context.Context) error

StartupHook is a function called before the server starts accepting connections. The context passed to the hook has a deadline based on any configured timeout.

Startup hooks execute sequentially in registration order. If any startup hook returns an error, the server will not start.

Hooks must respect context cancellation by checking ctx.Done(). If a hook blocks without respecting the context, startup will hang.

Example:

app.RegisterStartupHook("migrations", func(ctx context.Context) error {
    return goose.Up(db.DB, "migrations")
})

type StartupHookConfig added in v0.58.0

type StartupHookConfig struct {
	Name string
	Hook StartupHook
}

StartupHookConfig configures a startup hook.

type TLSConfig added in v0.58.0

type TLSConfig struct {
	// Addr is the address for the HTTPS server to listen on.
	// Default: "localhost:8443"
	Addr string
	// Server is the HTTPS server instance for encrypted traffic.
	// Default: preconfigured server listening on "localhost:8443"
	Server *http.Server

	// Listener allows specifying a custom net.Listener for HTTPS traffic (optional).
	// Default: nil (system default listener will be created)
	Listener net.Listener

	// CertFile is the file path to the TLS certificate (PEM) when serving HTTPS.
	// Default: "" (no certificate loaded unless specified)
	CertFile string

	// KeyFile is the file path to the TLS private key (PEM) when serving HTTPS.
	// Default: "" (no key loaded unless specified)
	KeyFile string

	// RedirectHTTP enables automatic HTTP to HTTPS redirects when both HTTP and
	// HTTPS servers are running. When enabled, all HTTP traffic is redirected
	// to HTTPS with a 301 Moved Permanently status.
	// Default: true (for security, redirects to HTTPS by default)
	RedirectHTTP bool
}

type TemplateManager added in v0.3.0

type TemplateManager struct {
	// contains filtered or unexported fields
}

TemplateManager implements TemplateRenderer using html/template

func (*TemplateManager) Render added in v0.3.0

func (tm *TemplateManager) Render(w http.ResponseWriter, code int, name string, data any) error

Render renders the specified template with the given data and status code

type TemplateRenderer added in v0.3.0

type TemplateRenderer interface {
	Render(w http.ResponseWriter, code int, name string, data any) error
}

TemplateRenderer defines the interface for rendering HTML templates

func NewTemplateManager added in v0.3.0

func NewTemplateManager(templatesFS embed.FS, pattern string) TemplateRenderer

NewTemplateManager creates a new TemplateManager with parsed templates from the embedded filesystem

type ValidationError

type ValidationError = problem.ValidationError

ValidationError is an alias to problem.ValidationError. It represents a single validation error with optional field location information.

type Validator added in v0.18.0

type Validator interface {
	// Struct validates a struct using `validate` struct tags.
	// It returns an error containing all validation failures, or nil if valid.
	Struct(dst any) error

	// Register adds a custom validation function with the given name.
	// The name can be used in struct tags like `validate:"customName"`.
	Register(name string, fn func(reflect.Value, string) error)
}

Validator is the interface for struct validation. Users can implement this interface to provide their own validation logic (e.g., wrapping github.com/go-playground/validator/v10).

Example with go-playground/validator:

import "github.com/go-playground/validator/v10"

type myValidator struct {
    v *validator.Validate
}

func (m *myValidator) Struct(dst any) error {
    return m.v.Struct(dst)
}

func (m *myValidator) Register(name string, fn func(reflect.Value, string) error) {
    // Custom registration or no-op
}

app := zerohttp.New(zh.ConfigValidator: &myValidator{v: validator.New()}})

Directories

Path Synopsis
examples
core/crud command
core/graceful command
core/hsts command
core/lifecycle command
core/logger command
core/rendering command
core/static_spa command
core/template command
core/testing command
core/tls command
healthcheck command
metrics command
middleware/cors command
middleware/csrf command
middleware/etag command
middleware/host command
pagination command
pprof command
sse command
Package extensions provides optional interfaces for extending zerohttp functionality.
Package extensions provides optional interfaces for extending zerohttp functionality.
autocert
Package autocert provides automatic TLS certificate management via Let's Encrypt.
Package autocert provides automatic TLS certificate management via Let's Encrypt.
http3
Package http3 provides HTTP/3 support for zerohttp.
Package http3 provides HTTP/3 support for zerohttp.
websocket
Package websocket provides WebSocket support for zerohttp.
Package websocket provides WebSocket support for zerohttp.
webtransport
Package webtransport provides WebTransport support for zerohttp.
Package webtransport provides WebTransport support for zerohttp.
Package healthcheck provides [Kubernetes]-style health probe endpoints.
Package healthcheck provides [Kubernetes]-style health probe endpoints.
Package httpx provides HTTP header names, content types, and other constants for use throughout the framework.
Package httpx provides HTTP header names, content types, and other constants for use throughout the framework.
internal
mwutil
Package mwutil provides shared utilities for middleware implementations.
Package mwutil provides shared utilities for middleware implementations.
Package log provides structured logging interfaces for zerohttp.
Package log provides structured logging interfaces for zerohttp.
Package metrics provides [Prometheus]-compatible metrics collection with zero external dependencies.
Package metrics provides [Prometheus]-compatible metrics collection with zero external dependencies.
Package middleware provides HTTP middleware for zerohttp.
Package middleware provides HTTP middleware for zerohttp.
basicauth
Package basicauth provides HTTP Basic Authentication middleware.
Package basicauth provides HTTP Basic Authentication middleware.
cache
Package cache provides HTTP caching middleware with ETag and conditional request support.
Package cache provides HTTP caching middleware with ETag and conditional request support.
circuitbreaker
Package circuitbreaker provides circuit breaker middleware for fault tolerance.
Package circuitbreaker provides circuit breaker middleware for fault tolerance.
compress
Package compress provides response compression middleware.
Package compress provides response compression middleware.
contentcharset
Package contentcharset provides Content-Type charset validation middleware.
Package contentcharset provides Content-Type charset validation middleware.
contentencoding
Package contentencoding provides Content-Encoding validation middleware.
Package contentencoding provides Content-Encoding validation middleware.
contenttype
Package contenttype provides Content-Type validation middleware.
Package contenttype provides Content-Type validation middleware.
cors
Package cors provides Cross-Origin Resource Sharing middleware.
Package cors provides Cross-Origin Resource Sharing middleware.
csrf
Package csrf provides Cross-Site Request Forgery protection middleware.
Package csrf provides Cross-Site Request Forgery protection middleware.
etag
Package etag provides ETag generation and validation middleware.
Package etag provides ETag generation and validation middleware.
hmacauth
Package hmacauth provides HMAC request signing authentication.
Package hmacauth provides HMAC request signing authentication.
host
Package host provides host header validation middleware.
Package host provides host header validation middleware.
idempotency
Package idempotency provides idempotent request handling middleware.
Package idempotency provides idempotent request handling middleware.
jwtauth
Package jwtauth provides JWT authentication middleware.
Package jwtauth provides JWT authentication middleware.
mediatype
Package mediatype provides media type negotiation and validation middleware.
Package mediatype provides media type negotiation and validation middleware.
nocache
Package nocache provides cache-busting middleware.
Package nocache provides cache-busting middleware.
ratelimit
Package ratelimit provides rate limiting middleware.
Package ratelimit provides rate limiting middleware.
realip
Package realip provides client IP extraction middleware.
Package realip provides client IP extraction middleware.
recover
Package recover provides panic recovery middleware.
Package recover provides panic recovery middleware.
requestbodysize
Package requestbodysize provides request body size limiting middleware.
Package requestbodysize provides request body size limiting middleware.
requestid
Package requestid provides request ID generation middleware.
Package requestid provides request ID generation middleware.
requestlogger
Package requestlogger provides HTTP request/response logging middleware.
Package requestlogger provides HTTP request/response logging middleware.
reverseproxy
Package reverseproxy provides reverse proxy middleware.
Package reverseproxy provides reverse proxy middleware.
securityheaders
Package securityheaders provides security header middleware.
Package securityheaders provides security header middleware.
setheader
Package setheader provides custom response header middleware.
Package setheader provides custom response header middleware.
timeout
Package timeout provides request timeout middleware.
Package timeout provides request timeout middleware.
tracer
Package tracer provides distributed tracing middleware.
Package tracer provides distributed tracing middleware.
trailingslash
Package trailingslash provides trailing slash normalization middleware.
Package trailingslash provides trailing slash normalization middleware.
value
Package value provides context value injection middleware.
Package value provides context value injection middleware.
Package pagination provides utilities for handling pagination in HTTP APIs.
Package pagination provides utilities for handling pagination in HTTP APIs.
Package pprof provides Go runtime profiling endpoints.
Package pprof provides Go runtime profiling endpoints.
Package sse provides Server-Sent Events (SSE) support for real-time server-to-client streaming using Go's standard library.
Package sse provides Server-Sent Events (SSE) support for real-time server-to-client streaming using Go's standard library.
Package storage provides a shared storage interface for middleware backends.
Package storage provides a shared storage interface for middleware backends.
Package trace provides interfaces for distributed tracing.
Package trace provides interfaces for distributed tracing.
Package validator provides struct tag-based validation with no external dependencies.
Package validator provides struct tag-based validation with no external dependencies.
Package zhtest provides testing utilities for zerohttp applications.
Package zhtest provides testing utilities for zerohttp applications.

Jump to

Keyboard shortcuts

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