
Minimal app-facing HTTP abstractions, middleware, adapters, and route indexing for GoForj.
web is built on top of Echo, which is a fantastic HTTP framework with a fast router, strong middleware story, and a mature ecosystem. GoForj wraps it so applications can code against a smaller app-facing contract while still getting a high-quality underlying engine, reusable middleware packages, testing helpers, route indexing, and framework-owned integration points like Prometheus and generated wiring.
Using With GoForj Apps
Generated Apps use web through controllers, route groups, middleware, route listing, metrics, and inspects. Start with HTTP Services, Routes, and Controllers when you are building a full App.
Use this page when you need standalone package APIs, adapter details, middleware primitives, or webtest helpers.
Installation
go get github.com/goforj/webQuick Start
package main
import (
"fmt"
"log"
"net/http"
"github.com/goforj/web"
"github.com/goforj/web/adapter/echoweb"
"github.com/goforj/web/webmiddleware"
)
func main() {
adapter := echoweb.New()
router := adapter.Router()
router.Use(
webmiddleware.Recover(),
webmiddleware.RequestID(),
)
router.GET("/healthz", func(c web.Context) error {
// GET /healthz -> 200 ok
return c.Text(200, "ok")
})
router.GET("/users/:id", func(c web.Context) error {
// GET /users/42 -> 200 {"id":"42","name":"user-42"}
return c.JSON(200, map[string]any{
"id": c.Param("id"),
"name": fmt.Sprintf("user-%s", c.Param("id")),
})
})
// Boot the HTTP server with the adapter as the final handler.
log.Fatal(http.ListenAndServe(":8080", adapter))
}Common Patterns
Route Groups
adapter := echoweb.New()
router := adapter.Router()
routes := []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
// GET /api/healthz -> 204
return c.NoContent(http.StatusOK)
}),
web.NewRoute(http.MethodGet, "/users", func(c web.Context) error {
// GET /api/users -> 200 [{"id":1}]
return c.JSON(http.StatusOK, []map[string]any{{"id": 1}})
}),
}
group := web.NewRouteGroup("/api", routes)
if err := web.RegisterRoutes(router, []web.RouteGroup{group}); err != nil {
panic(err)
}Use Middleware
adapter := echoweb.New()
router := adapter.Router()
store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
router.Use(
webmiddleware.Recover(),
webmiddleware.RequestID(),
webmiddleware.RateLimiter(store),
)
router.GET("/api/messages", func(c web.Context) error {
// GET /api/messages -> 200 [{"id":1,"subject":"Welcome"}]
// Requests over the configured rate limit return 429.
return c.JSON(200, []map[string]any{
{"id": 1, "subject": "Welcome"},
})
})Test A Route
func TestHealthRoute(t *testing.T) {
adapter := echoweb.New()
router := adapter.Router()
router.GET("/healthz", func(c web.Context) error {
return c.Text(200, "ok")
})
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
rec := httptest.NewRecorder()
adapter.ServeHTTP(rec, req)
if rec.Code != 200 {
t.Fatalf("expected 200, got %d", rec.Code)
}
if strings.TrimSpace(rec.Body.String()) != "ok" {
t.Fatalf("expected ok, got %q", rec.Body.String())
}
// rec.Code -> 200
// rec.Body -> ok
}Expose Prometheus Metrics
adapter := echoweb.New()
router := adapter.Router()
metrics := webprometheus.MustNew(webprometheus.Config{Namespace: "app"})
router.Use(metrics.Middleware())
router.GET("/users", func(c web.Context) error {
// GET /users -> 204
return c.NoContent(http.StatusOK)
})
router.GET("/metrics", metrics.Handler())
// GET /metrics -> Prometheus text expositionGenerate A Route Index
_, err := webindex.Run(context.Background(), webindex.IndexOptions{
Root: ".",
OutPath: "webindex.json",
})
if err != nil {
panic(err)
}
// Writes webindex.json.Packages
web: app-facing interfaces, route registration, route reporting helpersadapter/echoweb: Echo-backed adapter and server bootstrapwebmiddleware: grouped HTTP middleware for auth, routing, payloads, rate limiting, and morewebprometheus: Prometheus middleware and scrape handlerwebindex: route manifest and OpenAPI index generationwebtest: lightweight handler testing context
API
API Index
API Reference
Generated from public API comments and examples.
Adapter
echoweb.Adapter.Echo
Echo returns the underlying Echo engine.
adapter := echoweb.New()
fmt.Println(adapter.Echo() != nil)
// trueechoweb.Adapter.Router
Router returns the app-facing router contract.
adapter := echoweb.New()
fmt.Println(adapter.Router() != nil)
// trueechoweb.Adapter.ServeHTTP
ServeHTTP exposes the adapter as a standard http.Handler.
adapter := echoweb.New()
adapter.Router().GET("/healthz", func(c web.Context) error { return c.NoContent(http.StatusOK) })
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
adapter.ServeHTTP(rr, req)
fmt.Println(rr.Code)
// 204echoweb.New
New creates a new Echo-backed web adapter.
adapter := echoweb.New()
fmt.Println(adapter.Router() != nil, adapter.Echo() != nil)
// true trueechoweb.NewServer
NewServer creates an Echo-backed server from web route groups and mounts.
server, err := echoweb.NewServer(echoweb.ServerConfig{
RouteGroups: []web.RouteGroup{
web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusOK) }),
}),
},
})
fmt.Println(err == nil, server.Router() != nil)
// true trueechoweb.Server.Router
Router exposes the app-facing router contract.
server, _ := echoweb.NewServer(echoweb.ServerConfig{})
fmt.Println(server.Router() != nil)
// trueechoweb.Server.Serve
Serve starts the server and gracefully shuts it down when ctx is cancelled.
server, _ := echoweb.NewServer(echoweb.ServerConfig{Addr: "127.0.0.1:0"})
ctx, cancel := context.WithCancel(context.Background())
cancel()
fmt.Println(server.Serve(ctx) == nil)
// trueechoweb.Server.ServeHTTP
ServeHTTP exposes the server as an http.Handler for tests and local probing.
server, _ := echoweb.NewServer(echoweb.ServerConfig{
RouteGroups: []web.RouteGroup{
web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return c.NoContent(http.StatusOK) }),
}),
},
})
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil)
server.ServeHTTP(rr, req)
fmt.Println(rr.Code)
// 204echoweb.UnwrapContext
UnwrapContext returns the underlying Echo context when the web.Context came from this adapter.
adapter := echoweb.New()
adapter.Router().GET("/healthz", func(c web.Context) error {
_, ok := echoweb.UnwrapContext(c)
fmt.Println(ok)
return c.NoContent(http.StatusOK)
})
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
adapter.ServeHTTP(rr, req)
// trueechoweb.UnwrapWebSocketConn
UnwrapWebSocketConn returns the underlying gorilla websocket connection.
_, ok := echoweb.UnwrapWebSocketConn(nil)
fmt.Println(ok)
// falseechoweb.Wrap
Wrap exposes an existing Echo engine through the web.Router contract.
adapter := echoweb.Wrap(nil)
fmt.Println(adapter.Echo() != nil)
// trueIndexing
webindex.Run
Run indexes API metadata from source and writes artifacts.
manifest, err := webindex.Run(context.Background(), webindex.IndexOptions{
Root: ".",
OutPath: "webindex.json",
})
fmt.Println(err == nil, manifest.Version != "")
// true trueAuth Middleware
webmiddleware.BasicAuth
BasicAuth returns basic auth middleware.
router := echoweb.New().Router()
router.Use(webmiddleware.BasicAuth(func(user, pass string, c web.Context) (bool, error) {
return user == "demo" && pass == "secret", nil
}))
router.GET("/admin", func(c web.Context) error {
return c.Text(200, "welcome")
})webmiddleware.BasicAuthWithConfig
BasicAuthWithConfig returns basic auth middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.BasicAuthWithConfig(webmiddleware.BasicAuthConfig{
Realm: "Admin",
Validator: func(user, pass string, c web.Context) (bool, error) {
return user == "demo" && pass == "secret", nil
},
}))
router.GET("/admin", func(c web.Context) error {
return c.Text(200, "welcome")
})webmiddleware.CSRF
CSRF enables token-based CSRF protection.
router := echoweb.New().Router()
router.Use(webmiddleware.CSRF())
router.POST("/settings", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.CSRFWithConfig
CSRFWithConfig enables token-based CSRF protection with config.
router := echoweb.New().Router()
router.Use(webmiddleware.CSRFWithConfig(webmiddleware.CSRFConfig{
CookieName: "_csrf",
TokenLookup: "header:X-CSRF-Token",
}))
router.POST("/settings", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.CreateExtractors
CreateExtractors creates extractors from a lookup definition.
extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token")
fmt.Println(err == nil, len(extractors))
// true 2webmiddleware.KeyAuth
KeyAuth returns key auth middleware.
router := echoweb.New().Router()
router.Use(webmiddleware.KeyAuth(func(key string, c web.Context) (bool, error) {
return key == "demo-key", nil
}))
router.GET("/api/reports", func(c web.Context) error {
return c.JSON(200, map[string]any{"ready": true})
})webmiddleware.KeyAuthWithConfig
KeyAuthWithConfig returns key auth middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.KeyAuthWithConfig(webmiddleware.KeyAuthConfig{
KeyLookup: "query:api_key",
Validator: func(key string, c web.Context) (bool, error) {
return key == "demo-key", nil
},
}))
router.GET("/api/reports", func(c web.Context) error {
return c.JSON(200, map[string]any{"ready": true})
})Compression Middleware
webmiddleware.Compress
Compress enables gzip response compression for clients that support it.
router := echoweb.New().Router()
router.Use(webmiddleware.Compress())
router.GET("/reports", func(c web.Context) error {
return c.Text(200, "large report response")
})webmiddleware.Decompress
Decompress inflates gzip-encoded request bodies before handlers read them.
router := echoweb.New().Router()
router.Use(webmiddleware.Decompress())
router.POST("/ingest", func(c web.Context) error {
data, _ := io.ReadAll(c.Request().Body)
return c.JSON(200, map[string]int{"bytes": len(data)})
})webmiddleware.DecompressWithConfig
DecompressWithConfig inflates gzip-encoded request bodies with custom options.
router := echoweb.New().Router()
router.Use(webmiddleware.DecompressWithConfig(webmiddleware.DecompressConfig{
Skipper: func(c web.Context) bool {
return c.Path() == "/webhooks/raw"
},
}))
router.POST("/ingest", func(c web.Context) error {
return c.NoContent(202)
})webmiddleware.Gzip
Gzip enables gzip response compression for clients that support it.
router := echoweb.New().Router()
router.GET("/feed", func(c web.Context) error {
return c.Text(200, "large feed response")
}, webmiddleware.Gzip())webmiddleware.GzipWithConfig
GzipWithConfig enables gzip response compression with custom options.
router := echoweb.New().Router()
router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{
MinLength: 1024,
}))Method Override Middleware
webmiddleware.MethodFromForm
MethodFromForm gets an override method from a form field.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromForm("_method"),
}))webmiddleware.MethodFromHeader
MethodFromHeader gets an override method from a request header.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromHeader("X-HTTP-Method-Override"),
}))webmiddleware.MethodFromQuery
MethodFromQuery gets an override method from a query parameter.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromQuery("_method"),
}))webmiddleware.MethodOverride
MethodOverride returns method override middleware.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverride())
router.PATCH("/articles/:id", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.MethodOverrideWithConfig
MethodOverrideWithConfig returns method override middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromQuery("_method"),
}))
router.DELETE("/articles/:id", func(c web.Context) error {
return c.NoContent(204)
})Path Rewriting Middleware
webmiddleware.AddTrailingSlash
AddTrailingSlash adds a trailing slash to the request path.
router := echoweb.New().Router()
router.Use(webmiddleware.AddTrailingSlash())
router.GET("/docs/", func(c web.Context) error {
return c.Text(200, "docs")
})webmiddleware.AddTrailingSlashWithConfig
AddTrailingSlashWithConfig returns trailing-slash middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.AddTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
RedirectCode: 308,
}))
router.GET("/docs/", func(c web.Context) error {
return c.Text(200, "docs")
})webmiddleware.RemoveTrailingSlash
RemoveTrailingSlash removes the trailing slash from the request path.
router := echoweb.New().Router()
router.Use(webmiddleware.RemoveTrailingSlash())
router.GET("/docs", func(c web.Context) error {
return c.Text(200, "docs")
})webmiddleware.RemoveTrailingSlashWithConfig
RemoveTrailingSlashWithConfig returns remove-trailing-slash middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.RemoveTrailingSlashWithConfig(webmiddleware.TrailingSlashConfig{
RedirectCode: 308,
}))
router.GET("/docs", func(c web.Context) error {
return c.Text(200, "docs")
})webmiddleware.Rewrite
Rewrite rewrites the request path using wildcard rules.
router := echoweb.New().Router()
router.Use(webmiddleware.Rewrite(map[string]string{
"/old/*": "/new/$1",
}))
router.GET("/new/:name", func(c web.Context) error {
return c.Text(200, c.Param("name"))
})webmiddleware.RewriteWithConfig
RewriteWithConfig rewrites the request path using wildcard and regex rules.
router := echoweb.New().Router()
router.Use(webmiddleware.RewriteWithConfig(webmiddleware.RewriteConfig{
Rules: map[string]string{"/old/*": "/v2/$1"},
}))
router.GET("/v2/:name", func(c web.Context) error {
return c.Text(200, c.Param("name"))
})Payloads Middleware
webmiddleware.BodyDump
BodyDump captures request and response payloads.
router := echoweb.New().Router()
router.Use(webmiddleware.BodyDump(func(c web.Context, reqBody, resBody []byte) {
log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
}))
router.POST("/webhooks", func(c web.Context) error {
return c.JSON(202, map[string]any{"queued": true})
})webmiddleware.BodyDumpWithConfig
BodyDumpWithConfig captures request and response payloads with config.
router := echoweb.New().Router()
router.Use(webmiddleware.BodyDumpWithConfig(webmiddleware.BodyDumpConfig{
Skipper: func(c web.Context) bool {
return c.Path() == "/healthz"
},
Handler: func(c web.Context, reqBody, resBody []byte) {
log.Printf("%s %s -> %d bytes", c.Method(), c.URI(), len(resBody))
},
}))webmiddleware.BodyLimit
BodyLimit returns middleware that limits request body size.
router := echoweb.New().Router()
router.Use(webmiddleware.BodyLimit("2MB"))
router.POST("/uploads", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.BodyLimitWithConfig
BodyLimitWithConfig returns body limit middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.BodyLimitWithConfig(webmiddleware.BodyLimitConfig{
Limit: "10MB",
}))
router.POST("/imports", func(c web.Context) error {
return c.NoContent(202)
})webmiddleware.ErrorBodyDump
ErrorBodyDump captures response bodies for non-2xx and non-3xx responses.
router := echoweb.New().Router()
router.Use(webmiddleware.ErrorBodyDump(func(c web.Context, status int, body []byte) {
log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
}))
router.GET("/reports/:id", func(c web.Context) error {
return c.Text(404, "report not found")
})webmiddleware.ErrorBodyDumpWithConfig
ErrorBodyDumpWithConfig captures response bodies for non-success responses with config.
router := echoweb.New().Router()
router.Use(webmiddleware.ErrorBodyDumpWithConfig(webmiddleware.ErrorBodyDumpConfig{
Skipper: func(c web.Context) bool {
return c.Path() == "/healthz"
},
Handler: func(c web.Context, status int, body []byte) {
log.Printf("%s %s failed with %d", c.Method(), c.URI(), status)
},
}))Proxying Middleware
webmiddleware.NewRandomBalancer
NewRandomBalancer creates a random proxy balancer.
target, _ := url.Parse("http://localhost:8080")
balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
fmt.Println(balancer.Next(nil).URL.Host)
// localhost:8080webmiddleware.NewRoundRobinBalancer
NewRoundRobinBalancer creates a round-robin proxy balancer.
target, _ := url.Parse("http://localhost:8080")
balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
fmt.Println(balancer.Next(nil).URL.Host)
// localhost:8080webmiddleware.Proxy
Proxy creates a proxy middleware.
target, _ := url.Parse("http://localhost:8080")
balancer := webmiddleware.NewRandomBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
router := echoweb.New().Router()
router.Use(webmiddleware.Proxy(balancer))webmiddleware.ProxyWithConfig
ProxyWithConfig creates a proxy middleware with config.
target, _ := url.Parse("http://localhost:8080")
balancer := webmiddleware.NewRoundRobinBalancer([]*webmiddleware.ProxyTarget{{URL: target}})
router := echoweb.New().Router()
router.Use(webmiddleware.ProxyWithConfig(webmiddleware.ProxyConfig{
Balancer: balancer,
Rewrite: map[string]string{
"/api/*": "/$1",
},
}))Rate Limiting Middleware
webmiddleware.NewRateLimiterMemoryStore
NewRateLimiterMemoryStore creates an in-memory rate limiter store.
store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
allowed1, _ := store.Allow("192.0.2.1")
allowed2, _ := store.Allow("192.0.2.1")
fmt.Println(allowed1, allowed2)
// true falsewebmiddleware.NewRateLimiterMemoryStoreWithConfig
NewRateLimiterMemoryStoreWithConfig creates an in-memory rate limiter store with config.
store := webmiddleware.NewRateLimiterMemoryStoreWithConfig(webmiddleware.RateLimiterMemoryStoreConfig{Rate: rate.Every(time.Second)})
allowed, _ := store.Allow("192.0.2.1")
fmt.Println(allowed)
// truewebmiddleware.RateLimiter
RateLimiter creates a rate limiting middleware.
store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
router := echoweb.New().Router()
router.Use(webmiddleware.RateLimiter(store))
router.POST("/api/messages", func(c web.Context) error {
return c.NoContent(202)
})webmiddleware.RateLimiterMemoryStore.Allow
Allow checks whether the given identifier is allowed through.
store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
allowed, err := store.Allow("127.0.0.1")
fmt.Println(err == nil, allowed)
// true truewebmiddleware.RateLimiterWithConfig
RateLimiterWithConfig creates a rate limiting middleware with config.
store := webmiddleware.NewRateLimiterMemoryStore(rate.Every(time.Second))
router := echoweb.New().Router()
router.Use(webmiddleware.RateLimiterWithConfig(webmiddleware.RateLimiterConfig{
Store: store,
IdentifierExtractor: func(c web.Context) (string, error) {
return c.Header("X-Account-ID"), nil
},
}))Redirects Middleware
webmiddleware.HTTPSNonWWWRedirect
HTTPSNonWWWRedirect redirects to https without www.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSNonWWWRedirect())webmiddleware.HTTPSNonWWWRedirectWithConfig
HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))webmiddleware.HTTPSRedirect
HTTPSRedirect redirects http requests to https.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSRedirect())
router.GET("/docs", func(c web.Context) error {
return c.Text(200, "docs")
})webmiddleware.HTTPSRedirectWithConfig
HTTPSRedirectWithConfig returns HTTPS redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))webmiddleware.HTTPSWWWRedirect
HTTPSWWWRedirect redirects to https + www.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSWWWRedirect())webmiddleware.HTTPSWWWRedirectWithConfig
HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))webmiddleware.NonWWWRedirect
NonWWWRedirect redirects to the non-www host.
router := echoweb.New().Router()
router.Use(webmiddleware.NonWWWRedirect())webmiddleware.NonWWWRedirectWithConfig
NonWWWRedirectWithConfig returns non-WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))webmiddleware.WWWRedirect
WWWRedirect redirects to the www host.
router := echoweb.New().Router()
router.Use(webmiddleware.WWWRedirect())webmiddleware.WWWRedirectWithConfig
WWWRedirectWithConfig returns WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))Reliability Middleware
webmiddleware.Recover
Recover returns middleware that recovers panics from the handler chain.
router := echoweb.New().Router()
router.Use(webmiddleware.Recover())
router.GET("/panic", func(c web.Context) error {
panic("boom")
})webmiddleware.RecoverWithConfig
RecoverWithConfig returns recover middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.RecoverWithConfig(webmiddleware.RecoverConfig{
DisableStack: true,
HandleError: func(c web.Context, err error, stack []byte) error {
return c.JSON(500, map[string]any{"error": "internal server error"})
},
}))Request Lifecycle Middleware
webmiddleware.ContextTimeout
ContextTimeout sets a timeout on the request context.
router := echoweb.New().Router()
router.Use(webmiddleware.ContextTimeout(2 * time.Second))
router.GET("/reports", func(c web.Context) error {
return c.JSON(200, map[string]any{"ready": true})
})webmiddleware.ContextTimeoutWithConfig
ContextTimeoutWithConfig sets a timeout on the request context with config.
router := echoweb.New().Router()
router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{
Timeout: time.Second,
}))webmiddleware.DefaultSkipper
DefaultSkipper always runs the middleware.
fmt.Println(webmiddleware.DefaultSkipper(nil))
// falsewebmiddleware.RequestID
RequestID returns middleware that sets a request id header and context value.
router := echoweb.New().Router()
router.Use(webmiddleware.RequestID())
router.GET("/healthz", func(c web.Context) error {
return c.JSON(200, map[string]any{
"request_id": c.Get("request_id"),
})
})webmiddleware.RequestIDWithConfig
RequestIDWithConfig returns RequestID middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{
TargetHeader: "X-Correlation-ID",
ContextKey: "correlation_id",
}))webmiddleware.RequestLoggerWithConfig
RequestLoggerWithConfig returns request logger middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.RequestLoggerWithConfig(webmiddleware.RequestLoggerConfig{
LogValuesFunc: func(c web.Context, values webmiddleware.RequestLoggerValues) error {
log.Printf("%s %s %d %s", values.Method, values.URI, values.Status, values.Latency)
return nil
},
}))
router.GET("/users/:id", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.Timeout
Timeout returns a response-timeout middleware.
router := echoweb.New().Router()
router.Use(webmiddleware.Timeout())
router.GET("/healthz", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.TimeoutWithConfig
TimeoutWithConfig returns a response-timeout middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.TimeoutWithConfig(webmiddleware.TimeoutConfig{
Timeout: time.Second,
ErrorMessage: "request timed out",
}))Security Middleware
webmiddleware.CORS
CORS returns Cross-Origin Resource Sharing middleware.
router := echoweb.New().Router()
router.Use(webmiddleware.CORS())
router.GET("/api/healthz", func(c web.Context) error {
return c.JSON(200, map[string]any{"ok": true})
})webmiddleware.CORSWithConfig
CORSWithConfig returns CORS middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.CORSWithConfig(webmiddleware.CORSConfig{
AllowOrigins: []string{"https://app.example.com"},
AllowMethods: []string{"GET", "POST", "PATCH"},
}))
router.GET("/api/healthz", func(c web.Context) error {
return c.JSON(200, map[string]any{"ok": true})
})webmiddleware.Secure
Secure sets security-oriented response headers.
router := echoweb.New().Router()
router.Use(webmiddleware.Secure())
router.GET("/", func(c web.Context) error {
return c.Text(200, "home")
})webmiddleware.SecureWithConfig
SecureWithConfig sets security-oriented response headers with config.
router := echoweb.New().Router()
router.Use(webmiddleware.SecureWithConfig(webmiddleware.SecureConfig{
ReferrerPolicy: "same-origin",
ContentSecurityPolicy: "default-src 'self'",
}))Static Files Middleware
webmiddleware.Static
Static serves static content from the provided root.
router := echoweb.New().Router()
router.Use(webmiddleware.Static("public"))
router.GET("/healthz", func(c web.Context) error {
return c.NoContent(204)
})webmiddleware.StaticWithConfig
StaticWithConfig serves static content using config.
router := echoweb.New().Router()
router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{
Root: "public",
HTML5: true,
}))Prometheus
webprometheus.Default
Default returns the package-level Prometheus metrics instance.
fmt.Println(webprometheus.Default() == webprometheus.Default())
// truewebprometheus.Handler
Handler returns the package-level Prometheus scrape handler.
registry := prometheus.NewRegistry()
counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
registry.MustRegister(counter)
counter.Inc()
metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
recorder := httptest.NewRecorder()
ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
_ = metrics.Handler()(ctx)
fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
// truewebprometheus.Metrics.Handler
Handler exposes the configured Prometheus metrics as a web.Handler.
registry := prometheus.NewRegistry()
counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "demo_total", Help: "demo counter"})
registry.MustRegister(counter)
counter.Inc()
metrics, _ := webprometheus.New(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: registry})
recorder := httptest.NewRecorder()
ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/metrics", nil), recorder, "/metrics", nil)
_ = metrics.Handler()(ctx)
fmt.Println(strings.Contains(recorder.Body.String(), "demo_total"))
// truewebprometheus.Metrics.Middleware
Middleware records Prometheus metrics for each request.
registry := prometheus.NewRegistry()
metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
_ = handler(ctx)
out := &bytes.Buffer{}
_ = webprometheus.WriteGatheredMetrics(out, registry)
fmt.Println(strings.Contains(out.String(), "example_requests_total"))
// truewebprometheus.Middleware
Middleware returns the package-level Prometheus middleware.
registry := prometheus.NewRegistry()
metrics, _ := webprometheus.New(webprometheus.Config{Registerer: registry, Gatherer: registry, Namespace: "example"})
handler := metrics.Middleware()(func(c web.Context) error { return c.NoContent(http.StatusNoContent) })
ctx := webtest.NewContext(httptest.NewRequest(http.MethodGet, "/healthz", nil), nil, "/healthz", nil)
_ = handler(ctx)
out := &bytes.Buffer{}
_ = webprometheus.WriteGatheredMetrics(out, registry)
fmt.Println(strings.Contains(out.String(), "example_requests_total"))
// truewebprometheus.MustNew
MustNew creates a Metrics instance and panics on registration errors.
metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()})
fmt.Println(metrics != nil)
// truewebprometheus.New
New creates a Metrics instance backed by Prometheus collectors.
metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"})
_ = metrics
fmt.Println(err == nil)
// truewebprometheus.RunPushGatewayGatherer
RunPushGatewayGatherer starts pushing collected metrics until the context finishes.
err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{})
fmt.Println(err != nil)
// truewebprometheus.WriteGatheredMetrics
WriteGatheredMetrics gathers collected metrics and writes them to the given writer.
var buf bytes.Buffer
err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry())
fmt.Println(err == nil)
// trueRoute Reporting
BuildRouteEntries
BuildRouteEntries builds a sorted slice of route entries from registered groups and extra entries.
entries := web.BuildRouteEntries([]web.RouteGroup{
web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
}),
})
fmt.Println(entries[0].Path, entries[0].Methods[0])
// /api/healthz GETRenderRouteTable
RenderRouteTable renders a route table using simple ASCII borders and ANSI colors.
table := web.RenderRouteTable([]web.RouteEntry{{
Path: "/api/healthz",
Handler: "monitoring.Healthz",
Methods: []string{"GET"},
}})
fmt.Println(strings.Contains(table, "/api/healthz"))
// trueRouting
MountRouter
MountRouter applies mount-style router configuration in declaration order.
adapter := echoweb.New()
err := web.MountRouter(adapter.Router(), []web.RouterMount{
func(r web.Router) error {
r.GET("/healthz", func(c web.Context) error { return nil })
return nil
},
})
fmt.Println(err == nil)
// trueNewRoute
NewRoute creates a new route using the app-facing web handler contract directly.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
return c.NoContent(http.StatusOK)
})
fmt.Println(route.Method(), route.Path())
// GET /healthzNewRouteGroup
NewRouteGroup wraps routes and their accompanied web middleware.
group := web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
})
fmt.Println(group.RoutePrefix(), len(group.Routes()))
// /api 1NewWebSocketRoute
NewWebSocketRoute creates a websocket route using the app-facing websocket handler contract.
route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
return nil
})
fmt.Println(route.IsWebSocket())
// trueRegisterRoutes
RegisterRoutes registers route groups onto a router.
adapter := echoweb.New()
groups := []web.RouteGroup{
web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
}),
}
err := web.RegisterRoutes(adapter.Router(), groups)
fmt.Println(err == nil)
// trueRoute.Handler
Handler returns the route handler.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error {
return c.NoContent(http.StatusCreated)
})
ctx := webtest.NewContext(nil, nil, "/healthz", nil)
_ = route.Handler()(ctx)
fmt.Println(ctx.StatusCode())
// 201Route.HandlerName
HandlerName returns the original handler name for route reporting.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
fmt.Println(route.HandlerName() != "")
// trueRoute.IsWebSocket
IsWebSocket reports whether this route upgrades to a websocket connection.
route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error { return nil })
fmt.Println(route.IsWebSocket())
// trueRoute.Method
Method returns the HTTP method.
route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil })
fmt.Println(route.Method())
// POSTRoute.MiddlewareNames
MiddlewareNames returns original middleware names for route reporting.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth")
fmt.Println(route.MiddlewareNames()[0])
// authRoute.Middlewares
Middlewares returns the route middleware slice.
route := web.NewRoute(
http.MethodGet,
"/healthz",
func(c web.Context) error { return nil },
func(next web.Handler) web.Handler { return next },
)
fmt.Println(len(route.Middlewares()))
// 1Route.Path
Path returns the path of the route.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
fmt.Println(route.Path())
// /healthzRoute.WebSocketHandler
WebSocketHandler returns the websocket route handler.
route := web.NewWebSocketRoute("/ws", func(c web.Context, conn web.WebSocketConn) error {
c.Set("ready", true)
return nil
})
ctx := webtest.NewContext(nil, nil, "/ws", nil)
err := route.WebSocketHandler()(ctx, nil)
fmt.Println(err == nil, ctx.Get("ready"))
// true trueRoute.WithMiddlewareNames
WithMiddlewareNames attaches reporting-only middleware names to the route.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }).WithMiddlewareNames("auth", "trace")
fmt.Println(len(route.MiddlewareNames()))
// 2RouteGroup.MiddlewareNames
MiddlewareNames returns original middleware names for route reporting.
group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth")
fmt.Println(group.MiddlewareNames()[0])
// authRouteGroup.Middlewares
Middlewares returns the middleware slice for the group.
group := web.NewRouteGroup("/api", nil, func(next web.Handler) web.Handler { return next })
fmt.Println(len(group.Middlewares()))
// 1RouteGroup.RoutePrefix
RoutePrefix returns the group prefix.
group := web.NewRouteGroup("/api", nil)
fmt.Println(group.RoutePrefix())
// /apiRouteGroup.Routes
Routes returns the routes in the group.
group := web.NewRouteGroup("/api", []web.Route{
web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil }),
})
fmt.Println(len(group.Routes()))
// 1RouteGroup.WithMiddlewareNames
WithMiddlewareNames attaches reporting-only middleware names to the group.
group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace")
fmt.Println(len(group.MiddlewareNames()))
// 2Testing
webtest.NewContext
NewContext creates a new test context around the provided request/recorder pair.
req := httptest.NewRequest(http.MethodGet, "/users/42?expand=roles", nil)
ctx := webtest.NewContext(req, nil, "/users/:id", webtest.PathParams{"id": "42"})
fmt.Println(ctx.Param("id"), ctx.Query("expand"))
// 42 roles