šŸ“š fursy - Awesome Go Library for Web Frameworks

Go Gopher mascot for fursy

HTTP router with type-safe generic handlers, automatic OpenAPI 3.1 generation from code, and RFC 9457 error responses

šŸ·ļø Web Frameworks
šŸ“‚ Routers
⭐ 0 stars
View on GitHub šŸ”—

Detailed Description of fursy

šŸ”„ FURSY

Fast Universal Routing System

Next-generation HTTP router for Go with blazing performance, type-safe handlers, and minimal dependencies.

Go Reference Go Report Card Tests codecov License: MIT Release


⚔ Quick Start

package main

import (
    "log"
    "net/http"

    "github.com/coregx/fursy"
)

func main() {
    router := fursy.New()

    // Optional: Set validator for automatic validation
    // router.SetValidator(validator.New())

    // Simple text response with convenience method
    router.GET("/", func(c *fursy.Context) error {
        return c.Text("Welcome to FURSY!")  // 200 OK
    })

    // GET with convenience method (200 OK)
    router.GET("/users/:id", func(c *fursy.Context) error {
        id := c.Param("id")
        return c.OK(map[string]string{
            "id":   id,
            "name": "User " + id,
        })
    })

    // POST with convenience method (201 Created)
    router.POST("/users", func(c *fursy.Context) error {
        username := c.Form("username")
        email := c.Form("email")

        user := map[string]string{
            "id":    "123",
            "name":  username,
            "email": email,
        }
        return c.Created(user)  // 201 Created - REST best practice!
    })

    // DELETE with convenience method (204 No Content)
    router.DELETE("/users/:id", func(c *fursy.Context) error {
        // Delete user...
        return c.NoContentSuccess()  // 204 No Content
    })

    // Query parameters
    router.GET("/search", func(c *fursy.Context) error {
        query := c.Query("q")
        page := c.QueryDefault("page", "1")
        return c.OK(map[string]string{
            "query": query,
            "page":  page,
        })
    })

    log.Println("Server starting on :8080...")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Note: The examples above use simple handlers with *Context. For type-safe generic handlers Box[Req, Res], see the Type-Safe Handlers section below!


🌟 Why FURSY?

Type-Safe Handlers (First in Go!)

func Handler(box *fursy.Box[Request, Response]) error {
    // Compile-time type safety
    // Automatic validation
    // Zero boilerplate
}

Native RFC 9457 Problem Details

{
  "type": "https://fursy.coregx.dev/problems/validation-error",
  "title": "Validation Failed",
  "status": 400,
  "errors": [...]
}

Built-in OpenAPI 3.1 Generation

spec := r.OpenAPI(fursy.OpenAPIConfig{
    Title: "My API",
    Version: "1.0.0",
})
// Complete OpenAPI 3.1 spec from code!

Minimal Dependencies

  • Core Routing: Zero external dependencies (stdlib only)
  • Middleware: Minimal deps (JWT: golang-jwt/jwt, RateLimit: x/time)
  • Plugins: Optional extensions (OpenTelemetry, validators)
  • Predictable, minimal security surface

Production-Ready Performance

  • 256 ns/op static routes, 326 ns/op parametric routes
  • 1 allocation/op (routing hot path)
  • ~10M req/s throughput (simple routes)
  • Zero-allocation radix tree routing
  • Efficient context pooling

šŸ“¦ Installation

go get github.com/coregx/fursy

Requirements: Go 1.25+


šŸš€ Features

  • āœ… High Performance Routing - 256-326 ns/op, 1 alloc/op
  • āœ… Type-Safe Generic Handlers - Box[Req, Res] with compile-time safety
  • āœ… Automatic Validation - Set once, validate everywhere with 100+ tags
  • āœ… Content Negotiation - RFC 9110 compliant, AI agent support
  • āœ… RFC 9457 Problem Details - Standardized error responses
  • āœ… Minimal Dependencies - Core routing: stdlib only, middleware: minimal deps
  • āœ… Middleware Pipeline - Next/Abort pattern, pre-allocated buffers
  • āœ… Route Groups - Nested groups with middleware inheritance
  • āœ… JWT Authentication - Token validation, claims extraction
  • āœ… Rate Limiting - Token bucket algorithm, per-IP/per-user
  • āœ… Security Headers - OWASP 2025 compliant (CSP, HSTS, etc.)
  • āœ… Circuit Breaker - Failure threshold, auto-recovery
  • āœ… Graceful Shutdown - Connection draining, Kubernetes-ready
  • āœ… Context Pooling - Memory-efficient, prevents leaks
  • āœ… Convenience Methods - REST-friendly shortcuts (OK, Created, NoContentSuccess)
  • āœ… Real-Time Communications - SSE + WebSocket via stream library
  • āœ… Database Integration - dbcontext pattern with transaction support
  • āœ… Production Boilerplate - Complete DDD example with real-time features

šŸŽ›ļø Middleware

FURSY includes 8 production-ready middleware with minimal dependencies. Core middleware have zero external dependencies (stdlib only), with only 2 exceptions: JWT (golang-jwt/jwt) and RateLimit (x/time).

Core Middleware (Zero Dependencies)

Logger

Structured logging with log/slog for comprehensive request tracking.

import (
    "log/slog"
    "github.com/coregx/fursy/middleware"
)

// Default configuration
router.Use(middleware.Logger())

// With configuration
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
router.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
    Logger: logger,
    SkipPaths: []string{"/health", "/metrics"},
}))

Features:

  • āœ… Structured logging with log/slog (stdlib)
  • āœ… Request method, path, status, latency, bytes written
  • āœ… Client IP extraction (X-Real-IP, X-Forwarded-For)
  • āœ… Skip paths or custom skip function
  • āœ… JSON or text format support
  • āœ… Zero external dependencies

Recovery

Panic recovery with stack traces and RFC 9457 Problem Details.

router.Use(middleware.Recovery())

// With stack traces (development)
router.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{
    IncludeStackTrace: true,
}))

Features:

  • āœ… Automatic panic recovery
  • āœ… Stack trace logging
  • āœ… RFC 9457 error responses
  • āœ… Custom error handler
  • āœ… Production-safe (no stack traces by default)
  • āœ… Zero external dependencies

CORS

Cross-Origin Resource Sharing (RFC-compliant, OWASP recommended).

router.Use(middleware.CORS())

// With custom config
router.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: "https://example.com,https://foo.com",
    AllowMethods: "GET,POST,PUT,DELETE",
    AllowHeaders: "Content-Type,Authorization",
    AllowCredentials: true,
    MaxAge: 12 * time.Hour,
}))

Features:

  • āœ… Wildcard origins (*) support
  • āœ… Preflight requests (OPTIONS) handling
  • āœ… Credentials support
  • āœ… Expose headers configuration
  • āœ… MaxAge caching
  • āœ… Zero external dependencies

BasicAuth

HTTP Basic Authentication with constant-time comparison.

router.Use(middleware.BasicAuth(middleware.BasicAuthConfig{
    Username: "admin",
    Password: "secret",
}))

// With custom validator
router.Use(middleware.BasicAuth(middleware.BasicAuthConfig{
    Validator: func(username, password string) bool {
        return checkDatabase(username, password)
    },
}))

Features:

  • āœ… Simple username/password validation
  • āœ… Custom validator function
  • āœ… Realm configuration
  • āœ… WWW-Authenticate header
  • āœ… Constant-time comparison (timing attack protection)
  • āœ… Zero external dependencies

Secure

OWASP 2025 security headers for production hardening.

router.Use(middleware.Secure())

// With custom config
router.Use(middleware.SecureWithConfig(middleware.SecureConfig{
    ContentSecurityPolicy:   "default-src 'self'; script-src 'self' 'unsafe-inline'",
    HSTSMaxAge:             31536000, // 1 year
    HSTSExcludeSubdomains:  false,
    XFrameOptions:          "DENY",
    ContentTypeNosniff:     "nosniff",
    ReferrerPolicy:         "strict-origin-when-cross-origin",
}))

Features (OWASP 2025):

  • āœ… Content-Security-Policy (CSP)
  • āœ… Strict-Transport-Security (HSTS)
  • āœ… X-Frame-Options
  • āœ… X-Content-Type-Options: nosniff
  • āœ… X-XSS-Protection (deprecated, not set by default)
  • āœ… Referrer-Policy
  • āœ… Cross-Origin-Embedder-Policy
  • āœ… Cross-Origin-Opener-Policy
  • āœ… Cross-Origin-Resource-Policy
  • āœ… Permissions-Policy

Coverage: 100% Dependencies: Zero (stdlib only)


Authentication & Rate Limiting

JWT

JWT token validation with algorithm confusion prevention.

// Simple usage with signing key
router.Use(middleware.JWT([]byte("your-secret-key")))

// With full configuration
router.Use(middleware.JWTWithConfig(middleware.JWTConfig{
    SigningKey:    []byte("your-secret-key"),
    SigningMethod: "HS256",
    TokenLookup:  "header:Authorization",
}))

// With issuer and audience validation
router.Use(middleware.JWTWithConfig(middleware.JWTConfig{
    SigningKey:       []byte("secret"),
    SigningMethod:    "HS256",
    ValidateIssuer:   "my-app",
    ValidateAudience: "api",
}))

Features:

  • āœ… Algorithms: HS256, HS384, HS512, RS256, ES256
  • āœ… Token from Header/Query/Cookie
  • āœ… Issuer/Audience validation
  • āœ… Algorithm confusion prevention (forbids "none")
  • āœ… Custom claims support
  • āœ… Expiration time validation

Dependency: github.com/golang-jwt/jwt/v5


RateLimit

Token bucket rate limiting with RFC-compliant headers.

// Simple usage (100 req/s, burst of 200)
router.Use(middleware.RateLimit(100, 200))

// With full configuration and custom key function
router.Use(middleware.RateLimitWithConfig(middleware.RateLimitConfig{
    Rate:  10,
    Burst: 20,
    KeyFunc: func(c *fursy.Context) string {
        // Rate limit by user ID
        return c.GetString("user_id")
    },
}))

Features:

  • āœ… Token bucket algorithm (golang.org/x/time/rate)
  • āœ… Per-IP or custom key function
  • āœ… RFC headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset)
  • āœ… In-memory store with automatic cleanup
  • āœ… Custom error handler
  • āœ… Configurable retry-after header

Dependency: golang.org/x/time/rate


Resilience

CircuitBreaker

Zero-dependency circuit breaker for fault tolerance.

// Simple usage with defaults (5 consecutive failures, 60s timeout)
router.Use(middleware.CircuitBreaker())

// With consecutive failures threshold
router.Use(middleware.CircuitBreakerConsecutive(5, 30*time.Second))

// With ratio-based threshold (3 failures out of 10 requests)
router.Use(middleware.CircuitBreakerRatio(3, 10, 30*time.Second))

// With full configuration
router.Use(middleware.CircuitBreakerWithConfig(middleware.CircuitBreakerConfig{
    ConsecutiveFailures: 5,
    Timeout:             30 * time.Second,
    MaxRequests:         2, // Allow 2 requests in half-open state
}))

Features:

  • āœ… Zero external dependencies (pure Go)
  • āœ… Consecutive failures threshold
  • āœ… Ratio-based threshold
  • āœ… Time-window threshold
  • āœ… Half-open state with max requests
  • āœ… States: Closed → Open → Half-Open → Closed
  • āœ… Custom error handler
  • āœ… Thread-safe (concurrent request handling)

Coverage: 95.6% Dependencies: Zero (stdlib only)


Middleware Comparison

MiddlewareFURSYGinEchoFiber
Loggerāœ… log/slogāœ… Customāœ… Customāœ… Custom
Recoveryāœ… RFC 9457āœ… Basicāœ… Basicāœ… Basic
CORSāœ… Built-in (zero deps)šŸ”§ PluginšŸ”§ Plugināœ… Built-in
BasicAuthāœ… Built-ināœ… Built-ināœ… Built-ināœ… Built-in
JWTāœ… Built-inšŸ”§ PluginšŸ”§ Plugināœ… Built-in
Rate Limitāœ… Built-in (RFC headers)šŸ”§ PluginšŸ”§ Plugināœ… Built-in
Security Headersāœ… OWASP 2025āŒšŸ”§ Plugināœ… Basic
Circuit Breakerāœ… Zero depsāŒāŒāŒ
Test Coverage93.8%???
DependenciesCore: 0, JWT: 1, RateLimit: 1MultipleMultipleMultiple

Legend:

  • āœ… = Built-in with high quality implementation
  • šŸ”§ = Plugin/third-party required
  • āŒ = Not available

FURSY advantage: Production-ready middleware with minimal dependencies, OWASP 2025 compliance, RFC 9457 error responses, and comprehensive test coverage.


Learn More


šŸŽÆ Convenience Methods (REST Best Practices)

FURSY provides convenient shortcuts for common HTTP response patterns, following REST best practices:

Context Convenience Methods

// GET - 200 OK (most common)
router.GET("/users", func(c *fursy.Context) error {
    users := getAllUsers()
    return c.OK(users)  // Short for c.JSON(200, users)
})

// POST - 201 Created (resource creation)
router.POST("/users", func(c *fursy.Context) error {
    user := createUser(c)
    return c.Created(user)  // 201, not 200!
})

// DELETE - 204 No Content (successful deletion)
router.DELETE("/users/:id", func(c *fursy.Context) error {
    deleteUser(c.Param("id"))
    return c.NoContentSuccess()  // 204, no body
})

// Async operations - 202 Accepted
router.POST("/jobs", func(c *fursy.Context) error {
    jobID := startAsyncJob(c)
    return c.Accepted(map[string]string{"jobId": jobID})
})

// Simple text - 200 OK
router.GET("/ping", func(c *fursy.Context) error {
    return c.Text("pong")  // text/plain, 200
})

Why use convenience methods?

  • āœ… Less boilerplate - c.OK(data) vs c.JSON(200, data)
  • āœ… REST semantics - Created() clearly indicates 201, preventing mistakes
  • āœ… Self-documenting - Code intent is clear from method name
  • āœ… Flexibility - Original methods still available for custom status codes

For custom status codes, use explicit methods:

// Partial content - 206
return c.JSON(206, partialData)

// Custom redirect - 307
return c.Redirect(307, "/new-location")

Box Convenience Methods (Type-Safe)

// GET - 200 OK
router.GET[GetUserRequest, UserResponse]("/users/:id", func(b *fursy.Box[GetUserRequest, UserResponse]) error {
    user := getUser(b.ReqBody.ID)
    return b.OK(user)  // Type-safe 200 OK
})

// POST - 201 Created with Location header
router.POST[CreateUserRequest, UserResponse]("/users", func(b *fursy.Box[CreateUserRequest, UserResponse]) error {
    user := createUser(b.ReqBody)
    return b.Created("/users/"+user.ID, user)  // 201 + Location
})

// PUT - 200 OK with body
router.PUT[UpdateUserRequest, UserResponse]("/users/:id", func(b *fursy.Box[UpdateUserRequest, UserResponse]) error {
    updated := updateUser(b.ReqBody)
    return b.UpdatedOK(updated)  // Semantic clarity
})

// PUT - 204 No Content (no response body)
router.PUT[UpdateUserRequest, Empty]("/users/:id", func(b *fursy.Box[UpdateUserRequest, Empty]) error {
    updateUser(b.ReqBody)
    return b.UpdatedNoContent()  // 204, no body
})

// DELETE - 204 No Content
router.DELETE[Empty, Empty]("/users/:id", func(b *fursy.Box[Empty, Empty]) error {
    deleteUser(c.Param("id"))
    return b.NoContentSuccess()  // 204
})

Plugin Integration Methods

FURSY provides seamless integration with plugins through convenient Context methods:

Database Access

import (
    "github.com/coregx/fursy"
    "github.com/coregx/fursy/plugins/database"
)

// Setup database
sqlDB, _ := sql.Open("postgres", dsn)
db := database.NewDB(sqlDB)

router := fursy.New()
router.Use(database.Middleware(db))

// Access database in handlers
router.GET("/users/:id", func(c *fursy.Context) error {
    db := c.DB().(*database.DB)  // Type assertion

    var user User
    err := db.QueryRow(c.Request.Context(),
        "SELECT id, name FROM users WHERE id = $1", c.Param("id")).
        Scan(&user.ID, &user.Name)

    if err == sql.ErrNoRows {
        return c.Problem(fursy.NotFound("User not found"))
    }
    return c.JSON(200, user)
})

Type-safe helper (recommended):

router.GET("/users/:id", func(c *fursy.Context) error {
    db, ok := database.GetDB(c)  // Type-safe retrieval
    if !ok {
        return c.Problem(fursy.InternalServerError("Database not configured"))
    }

    // Use db...
})

Server-Sent Events (SSE)

import (
    "github.com/coregx/fursy/plugins/stream"
    "github.com/coregx/stream/sse"
)

// Setup SSE hub
hub := sse.NewHub[Notification]()
go hub.Run()
defer hub.Close()

router.Use(stream.SSEHub(hub))

// SSE endpoint
router.GET("/events", func(c *fursy.Context) error {
    hub, _ := stream.GetSSEHub[Notification](c)

    return stream.SSEUpgrade(c, func(conn *sse.Conn) error {
        hub.Register(conn)
        defer hub.Unregister(conn)

        <-conn.Done()  // Wait for client disconnect
        return nil
    })
})

WebSocket

import (
    "github.com/coregx/fursy/plugins/stream"
    "github.com/coregx/stream/websocket"
)

// Setup WebSocket hub
hub := websocket.NewHub()
go hub.Run()
defer hub.Close()

router.Use(stream.WebSocketHub(hub))

// WebSocket endpoint
router.GET("/ws", func(c *fursy.Context) error {
    hub, _ := stream.GetWebSocketHub(c)

    return stream.WebSocketUpgrade(c, func(conn *websocket.Conn) error {
        hub.Register(conn)
        defer hub.Unregister(conn)

        for {
            msgType, data, err := conn.Read()
            if err != nil {
                return err
            }
            hub.Broadcast(data)  // Echo to all clients
        }
    }, nil)
})

See also:


šŸŽÆ Automatic Validation

FURSY provides type-safe automatic validation through the validator plugin, giving you compile-time type safety combined with runtime validation - a unique combination in the Go ecosystem.

Why FURSY Validation is Different

Traditional routers require manual validation on every handler:

// āŒ Manual validation (Gin, Echo, Fiber)
func CreateUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.BindJSON(&req); err != nil {  // No validation!
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    // Manual validation needed
    if req.Email == "" || !isValidEmail(req.Email) {
        c.JSON(400, gin.H{"error": "invalid email"})
        return
    }
    // ... repeat for every field
}

With FURSY's type-safe handlers, validation is automatic and guaranteed:

// āœ… Automatic validation (FURSY)
router.POST[CreateUserRequest, UserResponse]("/users",
    func(c *fursy.Box[CreateUserRequest, UserResponse]) error {
        if err := c.Bind(); err != nil {
            return err  // Automatic RFC 9457 error response
        }

        // c.ReqBody is ALREADY validated! āœ…
        user := createUser(c.ReqBody)
        return c.Created("/users/"+user.ID, user)
    })

Key advantages:

  • āœ… Set once, validate everywhere - No manual checks per handler
  • āœ… Compile-time type safety - Generics ensure request/response types match
  • āœ… RFC 9457 compliant - Standard error format with field-level details
  • āœ… 100+ validation tags - email, URL, UUID, min/max, and more

Quick Example

package main

import (
    "github.com/coregx/fursy"
    "github.com/coregx/fursy/plugins/validator"
)

type CreateUserRequest struct {
    Name  string `json:"name" validate:"required,min=3,max=50"`
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"required,gte=18,lte=120"`
}

type UserResponse struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    router := fursy.New()

    // Set validator once - applies to ALL handlers
    router.SetValidator(validator.New())

    // Type-safe handler with automatic validation
    router.POST[CreateUserRequest, UserResponse]("/users",
        func(c *fursy.Box[CreateUserRequest, UserResponse]) error {
            if err := c.Bind(); err != nil {
                return err  // Automatic RFC 9457 response
            }

            // c.ReqBody is validated and type-safe!
            user := createUser(c.ReqBody)
            return c.Created("/users/"+user.ID, user)
        })

    log.Fatal(http.ListenAndServe(":8080", router))
}

Validation Error Response

When validation fails, FURSY returns RFC 9457 Problem Details with field-level errors:

Request:

curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Jo","email":"invalid","age":15}'

Response (422 Unprocessable Entity):

{
  "type": "about:blank",
  "title": "Validation Failed",
  "status": 422,
  "detail": "3 field(s) failed validation",
  "errors": {
    "name": "Name must be at least 3 characters",
    "email": "Email must be a valid email address",
    "age": "Age must be 18 or greater"
  }
}

Comparison with Other Routers

FeatureFURSYGinEchoFiber
Type Safetyāœ… Compile-time (Box[Req, Res])āŒ Runtime onlyāŒ Runtime onlyāŒ Runtime only
Auto Validationāœ… Set once, validate allāŒ Manual per handlerāŒ Manual per handlerāŒ Manual per handler
Error Formatāœ… RFC 9457 (standard)āŒ Custom JSONāŒ Custom JSONāŒ Custom JSON
Setup Complexityāœ… One line (SetValidator)āŒ Validator + binding per routeāŒ Validator + binding per routeāŒ Validator + binding per route
Field-Level Errorsāœ… AutomaticšŸ”§ Manual mappingšŸ”§ Manual mappingšŸ”§ Manual mapping

Learn more: See Validator Plugin Documentation for custom validators, nested structs, and advanced features.


🌐 Content Negotiation

FURSY provides RFC 9110 compliant content negotiation, enabling your API to respond in multiple formats based on the client's Accept header. This is essential for building modern APIs that serve both humans (HTML/Markdown) and machines (JSON/XML).

Why Content Negotiation Matters

Modern APIs need to support multiple clients:

  • Web Browsers → HTML
  • API Clients → JSON
  • AI Agents → Markdown (for better understanding)
  • Legacy Systems → XML

FURSY handles this automatically using RFC 9110 standards with quality values (q-parameters).

Automatic Format Selection

The simplest approach - FURSY picks the best format automatically:

router.GET("/users/:id", func(c *fursy.Context) error {
    user := getUser(c.Param("id"))

    // Automatically selects format based on Accept header
    // Supports: JSON, HTML, XML, Text, Markdown
    return c.Negotiate(200, user)
})

Client requests:

# JSON (default)
curl http://localhost:8080/users/123
# → Content-Type: application/json

# HTML
curl -H "Accept: text/html" http://localhost:8080/users/123
# → Content-Type: text/html

# XML
curl -H "Accept: application/xml" http://localhost:8080/users/123
# → Content-Type: application/xml

Explicit Format Control

For finer control, check what the client accepts:

router.GET("/docs", func(c *fursy.Context) error {
    // Check if client accepts markdown
    if c.Accepts(fursy.MIMETextMarkdown) {
        docs := generateMarkdownDocs()
        return c.Markdown(docs)  // AI-friendly format
    }

    // Fallback to JSON
    return c.OK(map[string]string{"message": "Use Accept: text/markdown for docs"})
})

Quality Values (q-parameter)

RFC 9110 defines quality values to prioritize formats:

router.GET("/api/data", func(c *fursy.Context) error {
    data := getData()

    // Client sends: Accept: text/html;q=0.9, application/json;q=1.0
    // FURSY automatically picks JSON (higher q-value)
    format := c.AcceptsAny(
        fursy.MIMEApplicationJSON,  // q=1.0
        fursy.MIMETextHTML,          // q=0.9
        fursy.MIMETextMarkdown,      // fallback
    )

    switch format {
    case fursy.MIMEApplicationJSON:
        return c.JSON(200, data)
    case fursy.MIMETextHTML:
        return c.HTML(200, renderHTML(data))
    case fursy.MIMETextMarkdown:
        return c.Markdown(formatMarkdown(data))
    default:
        return c.OK(data)  // Default to JSON
    }
})

Supported Formats

FormatMIME TypeConstantUse Case
JSONapplication/jsonMIMEApplicationJSONAPI responses (default)
HTMLtext/htmlMIMETextHTMLWeb browsers
XMLapplication/xmlMIMEApplicationXMLLegacy systems
Plain Texttext/plainMIMETextPlainSimple data
Markdowntext/markdownMIMETextMarkdownAI agents, documentation

AI Agent Support

FURSY has first-class support for AI agents via Markdown responses:

router.GET("/api/schema", func(c *fursy.Context) error {
    // AI agents prefer markdown for better understanding
    if c.Accepts(fursy.MIMETextMarkdown) {
        schema := `
# API Schema

## Users Endpoint
- **GET** /users - List all users
- **POST** /users - Create new user
  - Required: name (string), email (string)

## Authentication
All endpoints require Bearer token in Authorization header.
`
        return c.Markdown(schema)
    }

    // Regular clients get JSON
    return c.JSON(200, getOpenAPISchema())
})

Why Markdown for AI?

  • āœ… Better semantic understanding than JSON
  • āœ… Preserves structure (headers, lists, code blocks)
  • āœ… More context for LLMs to understand API behavior
  • āœ… Human-readable for debugging

Comparison with Other Routers

FeatureFURSYGinEchoFiber
RFC 9110 Complianceāœ… FullšŸ”§ PartialšŸ”§ PartialšŸ”§ Partial
Automatic Negotiationāœ… Negotiate()āŒ ManualšŸ”§ c.Format()āŒ Manual
Quality Values (q)āœ… AutomaticāŒ NoāŒ NoāŒ No
Accept Helpersāœ… Accepts(), AcceptsAny()āŒ NoāŒ Noāœ… c.Accepts()
Markdown Supportāœ… Built-ināŒ ManualāŒ ManualāŒ Manual
AI Agent Readyāœ… YesāŒ NoāŒ NoāŒ No

FURSY advantage: Only router with full RFC 9110 compliance, automatic q-value handling, and built-in AI agent support.

Learn more: See RFC 9110 - HTTP Semantics (Content Negotiation) for the complete specification.


šŸ“Š Observability

FURSY provides production-ready observability through the OpenTelemetry plugin, giving you complete visibility into your HTTP services with distributed tracing and metrics.

Why Observability Matters

Modern distributed systems require:

  • Distributed Tracing → Track requests across microservices
  • Performance Metrics → Monitor latency, throughput, errors
  • Error Tracking → Automatic error recording and status tracking
  • Production Debugging → Understand behavior in real-time

FURSY's OpenTelemetry plugin provides all of this with zero boilerplate - just add middleware.

Distributed Tracing

Track every request with W3C Trace Context propagation:

import (
    "context"
    "github.com/coregx/fursy"
    "github.com/coregx/fursy/plugins/opentelemetry"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

func main() {
    // Initialize OpenTelemetry tracer
    exporter, _ := jaeger.New(jaeger.WithCollectorEndpoint(
        jaeger.WithEndpoint("http://localhost:14268/api/traces"),
    ))
    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
    )
    otel.SetTracerProvider(tp)
    defer tp.Shutdown(context.Background())

    // Add tracing middleware - that's it!
    router := fursy.New()
    router.Use(opentelemetry.Middleware("my-service"))

    router.GET("/users/:id", func(c *fursy.Context) error {
        // Automatically traced! Span includes:
        // - HTTP method, path, status
        // - Request/response headers
        // - Duration
        // - Errors (if any)
        user := getUser(c.Param("id"))
        return c.OK(user)
    })

    http.ListenAndServe(":8080", router)
}

Features:

  • āœ… W3C Trace Context - Automatic propagation across services
  • āœ… HTTP Semantic Conventions - Full OpenTelemetry compliance
  • āœ… Error Recording - Automatic error and status tracking
  • āœ… Zero Overhead Filtering - Skip health checks and metrics endpoints

Metrics Collection

Track HTTP performance with Prometheus-compatible metrics:

import (
    "github.com/coregx/fursy"
    "github.com/coregx/fursy/plugins/opentelemetry"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/prometheus"
    sdkmetric "go.opentelemetry.io/otel/sdk/metric"
)

func main() {
    // Initialize Prometheus exporter
    exporter, _ := prometheus.New()
    mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(exporter))
    otel.SetMeterProvider(mp)

    // Add metrics middleware
    router := fursy.New()
    router.Use(opentelemetry.Metrics("my-service"))

    // Metrics automatically collected:
    // - http.server.request.duration (histogram)
    // - http.server.request.count (counter)
    // - http.server.request.size (histogram)
    // - http.server.response.size (histogram)

    router.GET("/users", func(c *fursy.Context) error {
        users := getAllUsers()
        return c.OK(users)
    })

    // Expose metrics at /metrics
    router.GET("/metrics", promhttp.Handler())

    http.ListenAndServe(":8080", router)
}

Available Metrics:

MetricTypeDescriptionLabels
http.server.request.durationHistogramRequest latencymethod, status, server
http.server.request.countCounterTotal requestsmethod, status
http.server.request.sizeHistogramRequest body sizemethod
http.server.response.sizeHistogramResponse body sizemethod, status

Cardinality Management: All metrics use low-cardinality labels (method, status, server) to prevent metrics explosion.

Custom Spans for Business Logic

Add custom spans to trace specific operations:

import "go.opentelemetry.io/otel"

router.GET("/users/:id", func(c *fursy.Context) error {
    // HTTP request span is created automatically by middleware

    // Add custom span for database query
    ctx := c.Request.Context()
    tracer := otel.Tracer("my-service")
    ctx, span := tracer.Start(ctx, "database.get_user")
    defer span.End()

    user := db.GetUser(ctx, c.Param("id"))

    // Add custom span for external API call
    _, apiSpan := tracer.Start(ctx, "api.enrich_user_data")
    enrichedData := api.Enrich(user)
    apiSpan.End()

    return c.OK(enrichedData)
})

Jaeger Integration Example

Complete setup with Jaeger for local development:

# Start Jaeger all-in-one (includes UI)
docker run -d --name jaeger \
  -e COLLECTOR_OTLP_ENABLED=true \
  -p 16686:16686 \
  -p 14268:14268 \
  jaegertracing/all-in-one:latest

# View traces at http://localhost:16686

Your fursy application will automatically send traces to Jaeger. No configuration changes needed!

Comparison with Other Routers

FeatureFURSYGinEchoFiber
OpenTelemetry Built-ināœ… PluginšŸ”§ Third-partyšŸ”§ Third-partyšŸ”§ Third-party
HTTP Semantic Conventionsāœ… FullšŸ”§ PartialšŸ”§ PartialšŸ”§ Partial
Metrics APIāœ… OpenTelemetryšŸ”§ Prometheus onlyšŸ”§ Prometheus onlyāœ… Built-in
Distributed Tracingāœ… W3C Trace ContextšŸ”§ ManualšŸ”§ ManualšŸ”§ Manual
Cardinality Managementāœ… AutomaticāŒ ManualāŒ Manualāœ… Automatic
Zero-configāœ… One lineāŒ Multiple stepsāŒ Multiple stepsāœ… One line

FURSY advantage: Official OpenTelemetry plugin with full HTTP semantic conventions compliance and zero-config setup.

Learn more: See OpenTelemetry Plugin Documentation for advanced configuration, custom spans, and production patterns.


šŸ“– Documentation

Status: 🟔 In Development


šŸŽÆ Comparison

FeatureFURSYGinEchoChiFiber
Type-Safe Handlersāœ…āŒāŒāŒāŒ
Auto Validationāœ…šŸ”§ ManualšŸ”§ ManualšŸ”§ ManualšŸ”§ Manual
Content Negotiationāœ… RFC 9110šŸ”§ PartialšŸ”§ PartialāŒšŸ”§ Partial
Zero Deps (core)āœ…āŒāŒāœ…āŒ
OpenAPI Built-ināœ…šŸ”§ PluginšŸ”§ PluginšŸ”§ PluginšŸ”§ Plugin
RFC 9457 Errorsāœ…āŒāŒāŒāŒ
Performance⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Go Version1.25+1.13+1.17+1.16+1.17+

FURSY is unique: Only router combining furious performance, type-safe generics, automatic validation, RFC 9110 content negotiation, OpenAPI, and RFC 9457 with minimal dependencies.


šŸ“ˆ Status

Current Version: v0.3.3 (Production Ready)

Status: Production Ready - Complete ecosystem with real-time, database, and production examples

Coverage: 93.8% test coverage (total), 94.3% core, 97.7% binding, 95.6% middleware

Performance: 256 ns/op (static), 326 ns/op (parametric), 1 alloc/op

Roadmap:

āœ… v0.1.0          āœ… v0.2.0          āœ… v0.3.0             šŸŽÆ v1.0.0 LTS
(Foundation)     (Docs+Examples)  (Real-time+DB)         (TBD - After Full
                                                          API Stabilization)
    │                  │                 │                       │
    ā–¼                  ā–¼                 ā–¼                       ā–¼
Core Router        Documentation    Real-Time+DB            Stable API
Middleware         11 Examples      Production Ready        Long-Term Support
Production         Validation       2 Plugins               (NOT Rushing!)
Features           OpenAPI          DDD Boilerplate

Current Status: v0.3.3 Production Ready āœ… Ecosystem: stream v0.1.0 (SSE + WebSocket), 2 production plugins, 10 examples Next: v0.x.x feature releases as needed (Cache, more plugins, community tools) v1.0.0 LTS: After 6-12 months of production usage and full API stabilization


šŸ¤ Contributing

We welcome contributions! Please see:

Development Requirements:

  • Go 1.25+
  • golangci-lint
  • Follow git-flow branching model

Want to help?

  • ⭐ Star the repo
  • šŸ“¢ Share with others
  • šŸ› Report bugs or request features
  • šŸ’¬ Join discussions (coming soon)

šŸ“œ License

MIT License - see LICENSE file for details


šŸ”— Links


šŸ’” Inspiration

FURSY stands on the shoulders of giants:

Technical:

Philosophy:

  • Relica - Zero deps, type safety, quality

Special Thanks

Professor Ancha Baranova - This project would not have been possible without her invaluable help and support. Her assistance was crucial in making all coregx projects a reality.


šŸ“ž Contact

Questions? Check back soon for:

  • GitHub Discussions
  • Discord server
  • Documentation site

šŸ”„ FURSY - Unleash Routing Fursy

Blazing Fast • Minimal Dependencies • Type-Safe • Furious


Built with ā¤ļø by the coregx team

Version: v0.3.3 - Production Ready Ecosystem: stream v0.1.0 + 2 plugins + 10 examples + DDD boilerplate Next: v1.0.0 LTS (after full API stabilization)