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.
go get github.com/goforj/webpackage 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))
}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)
}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"},
})
})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
}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 exposition_, err := webindex.Run(context.Background(), webindex.IndexOptions{
Root: ".",
OutPath: "webindex.json",
})
if err != nil {
panic(err)
}
// Writes webindex.json.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
Generated from public API comments and examples.
Echo returns the underlying Echo engine.
adapter := echoweb.New()
fmt.Println(adapter.Echo() != nil)
// trueRouter returns the app-facing router contract.
adapter := echoweb.New()
fmt.Println(adapter.Router() != nil)
// trueServeHTTP 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)
// 204New creates a new Echo-backed web adapter.
adapter := echoweb.New()
fmt.Println(adapter.Router() != nil, adapter.Echo() != nil)
// true trueNewServer 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 trueRouter exposes the app-facing router contract.
server, _ := echoweb.NewServer(echoweb.ServerConfig{})
fmt.Println(server.Router() != nil)
// trueServe 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)
// trueServeHTTP 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)
// 204UnwrapContext 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)
// trueUnwrapWebSocketConn returns the underlying gorilla websocket connection.
_, ok := echoweb.UnwrapWebSocketConn(nil)
fmt.Println(ok)
// falseWrap exposes an existing Echo engine through the web.Router contract.
adapter := echoweb.Wrap(nil)
fmt.Println(adapter.Echo() != nil)
// trueRun 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 trueBasicAuth 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")
})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")
})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)
})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)
})CreateExtractors creates extractors from a lookup definition.
extractors, err := webmiddleware.CreateExtractors("header:X-API-Key,query:token")
fmt.Println(err == nil, len(extractors))
// true 2KeyAuth 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})
})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})
})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")
})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)})
})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)
})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())GzipWithConfig enables gzip response compression with custom options.
router := echoweb.New().Router()
router.Use(webmiddleware.GzipWithConfig(webmiddleware.GzipConfig{
MinLength: 1024,
}))MethodFromForm gets an override method from a form field.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromForm("_method"),
}))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"),
}))MethodFromQuery gets an override method from a query parameter.
router := echoweb.New().Router()
router.Use(webmiddleware.MethodOverrideWithConfig(webmiddleware.MethodOverrideConfig{
Getter: webmiddleware.MethodFromQuery("_method"),
}))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)
})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)
})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")
})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")
})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")
})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")
})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"))
})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"))
})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})
})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))
},
}))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)
})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)
})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")
})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)
},
}))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:8080NewRoundRobinBalancer 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:8080Proxy 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))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",
},
}))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 falseNewRateLimiterMemoryStoreWithConfig 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)
// trueRateLimiter 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)
})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 trueRateLimiterWithConfig 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
},
}))HTTPSNonWWWRedirect redirects to https without www.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSNonWWWRedirect())HTTPSNonWWWRedirectWithConfig returns HTTPS non-WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSNonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))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")
})HTTPSRedirectWithConfig returns HTTPS redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))HTTPSWWWRedirect redirects to https + www.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSWWWRedirect())HTTPSWWWRedirectWithConfig returns HTTPS+WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.HTTPSWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))NonWWWRedirect redirects to the non-www host.
router := echoweb.New().Router()
router.Use(webmiddleware.NonWWWRedirect())NonWWWRedirectWithConfig returns non-WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.NonWWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))WWWRedirect redirects to the www host.
router := echoweb.New().Router()
router.Use(webmiddleware.WWWRedirect())WWWRedirectWithConfig returns WWW redirect middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.WWWRedirectWithConfig(webmiddleware.RedirectConfig{
Code: 307,
}))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")
})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"})
},
}))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})
})ContextTimeoutWithConfig sets a timeout on the request context with config.
router := echoweb.New().Router()
router.Use(webmiddleware.ContextTimeoutWithConfig(webmiddleware.ContextTimeoutConfig{
Timeout: time.Second,
}))DefaultSkipper always runs the middleware.
fmt.Println(webmiddleware.DefaultSkipper(nil))
// falseRequestID 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"),
})
})RequestIDWithConfig returns RequestID middleware with config.
router := echoweb.New().Router()
router.Use(webmiddleware.RequestIDWithConfig(webmiddleware.RequestIDConfig{
TargetHeader: "X-Correlation-ID",
ContextKey: "correlation_id",
}))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)
})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)
})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",
}))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})
})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})
})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")
})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 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)
})StaticWithConfig serves static content using config.
router := echoweb.New().Router()
router.Use(webmiddleware.StaticWithConfig(webmiddleware.StaticConfig{
Root: "public",
HTML5: true,
}))Default returns the package-level Prometheus metrics instance.
fmt.Println(webprometheus.Default() == webprometheus.Default())
// trueHandler 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"))
// trueHandler 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"))
// trueMiddleware 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"))
// trueMiddleware 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"))
// trueMustNew creates a Metrics instance and panics on registration errors.
metrics := webprometheus.MustNew(webprometheus.Config{Registerer: prometheus.NewRegistry(), Gatherer: prometheus.NewRegistry()})
fmt.Println(metrics != nil)
// trueNew creates a Metrics instance backed by Prometheus collectors.
metrics, err := webprometheus.New(webprometheus.Config{Namespace: "app"})
_ = metrics
fmt.Println(err == nil)
// trueRunPushGatewayGatherer starts pushing collected metrics until the context finishes.
err := webprometheus.RunPushGatewayGatherer(context.Background(), webprometheus.PushGatewayConfig{})
fmt.Println(err != nil)
// trueWriteGatheredMetrics gathers collected metrics and writes them to the given writer.
var buf bytes.Buffer
err := webprometheus.WriteGatheredMetrics(&buf, prometheus.NewRegistry())
fmt.Println(err == nil)
// trueBuildRouteEntries 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 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"))
// trueMountRouter 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 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 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 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 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)
// trueHandler 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())
// 201HandlerName 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() != "")
// trueIsWebSocket 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())
// trueMethod returns the HTTP method.
route := web.NewRoute(http.MethodPost, "/users", func(c web.Context) error { return nil })
fmt.Println(route.Method())
// POSTMiddlewareNames 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])
// authMiddlewares 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()))
// 1Path returns the path of the route.
route := web.NewRoute(http.MethodGet, "/healthz", func(c web.Context) error { return nil })
fmt.Println(route.Path())
// /healthzWebSocketHandler 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 trueWithMiddlewareNames 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()))
// 2MiddlewareNames returns original middleware names for route reporting.
group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth")
fmt.Println(group.MiddlewareNames()[0])
// authMiddlewares 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()))
// 1RoutePrefix returns the group prefix.
group := web.NewRouteGroup("/api", nil)
fmt.Println(group.RoutePrefix())
// /apiRoutes 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()))
// 1WithMiddlewareNames attaches reporting-only middleware names to the group.
group := web.NewRouteGroup("/api", nil).WithMiddlewareNames("auth", "trace")
fmt.Println(len(group.MiddlewareNames()))
// 2NewContext 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