The high-performance API framework for Go
Write your handlers. Everything else is already done.
See it in action
From go get to a fully observable, documented API.
What you get for free #
Three lines replace thirty.
// Typical setup: wire each SDK separately
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
"github.com/prometheus/client_golang/prometheus"
"log/slog"
"os"
)
func setupObservability(ctx context.Context) {
res, _ := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceName("my-api"),
),
)
exp, _ := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("localhost:4317"),
)
tp := trace.NewTracerProvider(
trace.WithBatcher(exp),
trace.WithResource(res),
)
otel.SetTracerProvider(tp)
reg := prometheus.NewRegistry()
reg.MustRegister(collectors.NewGoCollector())
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)
// Then wrap every handler with middleware...
}// Rivaas: one block, all three pillars
app.WithObservability(
app.WithMetrics(),
app.WithTracing(
tracing.WithOTLP("localhost:4317")),
app.WithLogging(
logging.WithJSONHandler()),
)Full OpenTelemetry stack — metrics, traces, and structured logs — configured in one block.
// Swaggo: magic comments + codegen step
//
//go:generate swag init -g main.go -o docs
//
// @Summary Get user by ID
// @Tags users
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} User
// @Failure 404 {object} ErrorResponse
// @Router /users/{id} [get]
func GetUser(w http.ResponseWriter, r *http.Request) {
// ...
}
// Then: swag init, mount Swagger UI route// Rivaas: route-level docs, no codegen
a.GET("/users/:id", handlers.GetUser,
app.WithDoc(
openapi.WithSummary("Get user"),
openapi.WithResponse(
http.StatusOK, User{}),
openapi.WithTags("users"),
),
).WhereInt("id")Route-level OpenAPI docs — no magic comments, no codegen step, full IDE support.
// Manual: signal handling
go func() {
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGHUP)
for range sigCh {
if err := cfg.Reload(); err != nil {
log.Printf("reload failed: %v", err)
}
}
}()
// Manual: health endpoint
http.HandleFunc("/health", func(
w http.ResponseWriter,
r *http.Request,
) {
if db.Ping() != nil {
w.WriteHeader(503)
return
}
w.WriteHeader(200)
})// Rivaas: /livez + /readyz endpoints
app.WithHealthEndpoints(
app.WithReadinessCheck("db", dbPing),
),
// Rivaas: SIGHUP config reload
a.OnReload(func(ctx context.Context) error {
return cfg.Load(ctx)
})Kubernetes-ready liveness and readiness probes, plus SIGHUP config reload — built in.
// Manual: decode body, extract params, validate
func CreateUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "invalid id", 400)
return
}
var body CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "invalid JSON", 400)
return
}
if body.Name == "" {
http.Error(w, "name is required", 422)
return
}
if !strings.Contains(body.Email, "@") {
http.Error(w, "invalid email", 422)
return
}
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
// Finally use id, body, page...
}// Rivaas: struct tags define sources + rules
type CreateUserRequest struct {
ID int `path:"id"`
Page int `query:"page"`
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
func CreateUser(c *app.Context) {
req, err := app.Bind[CreateUserRequest](c)
if err != nil { c.Fail(err); return }
// id, body, page — all bound and validated
}One generic call binds path, query, headers, and body — then validates everything.
// Manual: format RFC 9457 problem details
func writeError(w http.ResponseWriter, r *http.Request,
status int, title, detail string,
) {
w.Header().Set("Content-Type",
"application/problem+json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]any{
"type": "/errors/" + strings.ToLower(title),
"title": title,
"status": status,
"detail": detail,
"instance": r.URL.Path,
})
}
// Call for every error, in every handler...// Rivaas: one option, RFC 9457 everywhere
app.WithErrorFormatter(
errors.NewRFC9457("https://api.example.com"),
)
// Any c.Fail(err) now returns:
// {
// "type": "https://api.example.com/errors/not-found",
// "title": "Not Found",
// "status": 404,
// "detail": "user 42 not found"
// }RFC 9457 problem details from a single option — every error gets consistent, spec-compliant formatting.
// Manual: wire each middleware separately
import (
"github.com/rs/cors"
"github.com/klauspost/compress/gzhttp"
"github.com/ulule/limiter/v3"
"github.com/google/uuid"
)
r.Use(cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "PUT"},
AllowCredentials: true,
}).Handler)
r.Use(gzhttp.GzipHandler)
r.Use(rateLimiterMiddleware)
r.Use(requestIDMiddleware)
r.Use(panicRecoveryMiddleware)// Rivaas: built-in, same import path
app.WithMiddleware(
cors.New(cors.WithOrigins("*")),
compression.New(),
ratelimit.New(ratelimit.WithRate(100)),
requestid.New(),
recovery.New(),
)Production middleware — CORS, compression, rate limiting, recovery — one import path, functional options.
Proven Performance #
Fast routing with zero allocations.
Independent benchmarks comparing rivaas/router against popular Go frameworks. All values are nanoseconds per operation (ns/op)—lower is better.
Loading benchmark data...
Full methodology and reproduction instructionsUse what you need #
Full framework or standalone packages. Same functional options, no lock-in.
// Use the full framework...
import "rivaas.dev/app"
// ...or just the packages you need
import "rivaas.dev/router"
import "rivaas.dev/binding"
import "rivaas.dev/validation"
// Every package has its own go.mod — no lock-in
// Same functional options everywhere
r, _ := router.New()
r.Use(cors.New(), compression.New(), recovery.New())
r.GET("/users/:id", getUser).WhereInt("id")
r.POST("/users", createUser)
r.Serve(":8080")app
Batteries-included web framework.
router
High-performance HTTP router.
binding
Request binding (JSON, form, query).
validation
Struct validation with JSON Schema.
config
Configuration with validation.
logging
Structured logging with slog.
metrics
OpenTelemetry metrics.
tracing
Distributed tracing with OTLP.
openapi
Automatic OpenAPI 3.0/3.1.
Wild Rhubarb of the Mountains
Named after a wild rhubarb plant that grows high in Iran's mountains, at 1,500–3,000 meters. It survives where little else can — yet has fed people for centuries.