Skip to content

alexferl/zerohttp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

201 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

Zero-dependency Go HTTP framework with secure-by-default middleware, built-in validation, and RFC 9457 error responses

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors