Zero-config, pure-Go HTTP request visualizer & debugger for local Go web development.
Zero-config, pure-Go HTTP request visualizer & debugger for local Go web development.
Runtime HTTP debugger for Go, with an MCP server so your coding agent can debug the app too.
Wrap your handler, get a local dashboard of every request. Point Claude Code or Cursor at the MCP endpoint, and the agent can list requests, replay them against your app, diff the response before and after a fix, and read the logs, SQL queries, and panic stacks that came with each capture.
google/pprof. Storage backends (postgres, redis, sqlite, mongodb) and telemetry are separate modules you pull in only when you use them.Upgrading from v0.2.1? See Upgrading below.
go get github.com/doganarif/govisual/v2The core has no database drivers, no gRPC, no OTel SDK. Add-ons install separately:
go get github.com/doganarif/govisual/mcp # MCP server for coding agents
go get github.com/doganarif/govisual/store/postgres # or redis, sqlite, mongodb
go get github.com/doganarif/govisual/telemetry # OpenTelemetry exportpackage main
import (
"net/http"
"github.com/doganarif/govisual/v2"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", userHandler)
handler := govisual.Wrap(mux,
govisual.WithRequestBodyLogging(true),
govisual.WithResponseBodyLogging(true),
)
http.ListenAndServe(":8080", handler)
}Open the dashboard at http://localhost:8080/__viz.
The mcp module serves captured traffic to any MCP client. Claude Code (or Cursor, or a local script) can call twelve tools against your running app: read the most recent error, pull the full crime scene for one request, replay it after you change the code, and diff the response.
package main
import (
"net/http"
gvmcp "github.com/doganarif/govisual/mcp"
"github.com/doganarif/govisual/v2"
"github.com/doganarif/govisual/v2/store"
)
func main() {
mux := http.NewServeMux()
// ...routes...
st := store.WithNotify(store.NewMemory(200))
app := govisual.Wrap(mux, govisual.WithStore(st))
root := http.NewServeMux()
root.Handle("/mcp", gvmcp.Handler(st, gvmcp.WithBaseURL("http://localhost:8080")))
root.Handle("/", app)
http.ListenAndServe(":8080", root)
}Register with Claude Code:
claude mcp add govisual --transport http http://localhost:8080/mcpTools: get_last_error, list_requests, get_request, search_requests, get_stats, get_debug_context, replay_request, diff_replay, await_request, save_as_test, copy_as_curl, clear_requests.
Responses are token-aware: bounded lists and diagnostics, capped body excerpts, and sizes reported so the agent knows when to ask for more. The endpoint is loopback-only by default. Replay, diff, and generated curl commands require and are pinned to WithBaseURL(...).
Full guide: docs/claude-code.md.
Wrap your database driver. Queries executed with the request's context land on that request's profile with duration, affected rows, and any error.
import (
"database/sql"
"github.com/doganarif/govisual/v2"
_ "github.com/mattn/go-sqlite3" // your driver of choice
)
sql.Register("sqlite3+viz", govisual.WrapDriver(&sqlite3.SQLiteDriver{}))
db, err := sql.Open("sqlite3+viz", "app.db")
// then in a handler:
rows, err := db.QueryContext(r.Context(), "SELECT ...")Enable govisual.WithProfiling(true) on the wrap call and the SQL panel shows up on the request drawer's Trace tab.
Wrap your http.RoundTripper and outbound calls made with the inbound request's context are attached to it.
client := &http.Client{Transport: govisual.WrapTransport(nil)}
resp, err := client.Do(req.WithContext(r.Context()))Wrap your slog.Handler. Every record logged with the request's context also lands on that request's Logs tab, capped at 200 lines per request.
logger := slog.New(govisual.SlogHandler(slog.NewJSONHandler(os.Stdout, nil)))
// in a handler:
logger.InfoContext(r.Context(), "cache miss", "key", key)Your base handler still receives every record unchanged.
Handler panics land on the captured request with the panic value and full stack, then the panic is re-raised so recovery middleware and net/http behave exactly as they would without govisual in the chain.
govisual.Event(r.Context(), "cache miss", "key", key, "tier", "redis")The event appears on the request's Logs tab and (with profiling on) inside the middleware trace.
http://localhost:8080/__viz by default. Customize with WithDashboardPath("/__debug").
localStorage.By default requests live in memory bounded by WithMaxRequests (100). For persistence, install one of the storage modules and pass its constructor to WithStore.
import (
"github.com/doganarif/govisual/store/postgres"
"github.com/doganarif/govisual/v2"
)
st, err := postgres.New(
"postgres://user:pass@localhost:5432/dbname?sslmode=disable",
"govisual_requests", // table, created and migrated automatically
500, // capacity
)
if err != nil {
log.Fatal(err)
}
handler := govisual.Wrap(mux, govisual.WithStore(st))Same shape for the others:
redis.New("redis://localhost:6379/0", 500, 86400) // capacity, TTL seconds
sqlite.New("requests.db", "govisual_requests", 500)
mongodb.New("mongodb://localhost:27017", "db", "coll", 500)If your app already registers a SQLite driver, reuse your own *sql.DB:
db, _ := sql.Open("sqlite3", "app.db")
st, err := sqlite.NewWithDB(db, "govisual_requests", 500)The v2 capture fields (logs, panic stacks, performance metrics) round-trip through every backend, including on tables that pre-date v2 (an ALTER TABLE runs on open).
Tracing lives in its own module so the OTel SDK and gRPC stay out of builds that don't use them.
go get github.com/doganarif/govisual/telemetrytraced, shutdown, err := telemetry.Wrap(mux, telemetry.Config{
ServiceName: "my-service",
ServiceVersion: "1.0.0",
Endpoint: "localhost:4317",
Insecure: true,
Exporter: "otlp", // or "stdout", "noop"
})
if err != nil {
log.Fatal(err)
}
defer shutdown(context.Background())
// Wrap the mux (not the final handler) so govisual's dashboard stays untraced.
handler := govisual.Wrap(traced)…Full option reference: docs/api-reference.md. Longer configuration guide: docs/configuration.md.
The dashboard sees every captured request and response, so v2 defaults are conservative:
WithAllowRemote(). Never expose a remote dashboard without WithBasicAuth, WithDashboardAuth, or equivalent authentication in an outer handler.WithReplayEnabled(true). Replays load a captured request by ID and are pinned to WithReplayBaseURL(...), or to a validated loopback dashboard origin when no base URL is configured. WithAllowRemote() requires an explicit replay base URL. Clients may edit the method, path, headers, and body, but cannot choose another destination host.WithSystemInfo(...). Environment variables are only exposed if you list them by name.WithBasicAuth(user, pass), or a custom check via WithDashboardAuth(func(*http.Request) bool).Three breaking changes:
github.com/doganarif/govisual becomes github.com/doganarif/govisual/v2. Update imports.WithMemoryStorage, WithPostgresStorage, WithSQLiteStorage, WithSQLiteStorageDB, WithRedisStorage, WithMongoDBStorage are gone. Install the backend module you want and pass the constructor to WithStore(...). See the Storage section above.WithOpenTelemetry, WithServiceName, WithServiceVersion, WithOTelEndpoint, WithOTelInsecure, WithOTelExporter are gone. Install github.com/doganarif/govisual/telemetry and wrap your mux with telemetry.Wrap(mux, telemetry.Config{...}) before handing it to govisual.Wrap.Also worth noting:
WithAllowRemote() if you actually want it reachable over the network, and pair it with auth.WithReplayEnabled(true).WithSystemInfo(...).Logs, PanicStack, and Host. If you're reading raw JSON from the API, these are new optional fields; existing consumers keep working.cd cmd/examples/basic && go run main.go # dashboard tour
cd cmd/examples/otel && go run main.go # OTel + telemetry module
cd cmd/examples/multistorage && go run main.go # postgres / redis / sqlite / mongo
cd cmd/examples/profiling && go run main.go # per-request profiling and flame graph
cd cmd/examples/tracing && go run main.go # middleware trace, Event, SQL captureMIT.
Pull requests welcome. See Contributing.
Built by Arif Dogan, production AI and backend engineer.
I help SaaS teams ship production AI features, fast backends, and reliable developer tools.
No open issues yet, or sync has not completed.