Documentation
¶
Overview ¶
Package fursy provides Context types for HTTP request handling.
Package fursy provides the Empty type for type-safe handlers without request/response bodies.
Package fursy provides common HTTP errors for the FURSY router.
Package fursy provides a high-performance HTTP router for Go 1.25+.
FURSY combines type-safe routing with modern Go features like generics, providing fast URL matching (<100ns), zero dependencies, and clean API.
Quick Start ¶
router := fursy.New()
router.GET("/users/:id", func(c *fursy.Box) error {
id := c.Param("id")
return c.String(200, "User ID: "+id)
})
http.ListenAndServe(":8080", router)
Route Types ¶
FURSY supports three types of routes:
- Static: /users
- Parameters: /users/:id
- Wildcards: /files/*path
Performance ¶
FURSY uses a radix tree for routing, providing <100ns lookups and zero allocations for simple routes.
HTTP Methods ¶
All standard HTTP methods are supported: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
URL Parameters ¶
Extract parameters using Box methods:
id := c.Param("id")
page := c.Query("page")
username := c.Form("username")
Error Handling ¶
- 404 Not Found: Automatic for unregistered routes
- 405 Method Not Allowed: Automatic when route exists but method differs
See Router documentation for more details.
Index ¶
- Constants
- Variables
- func DELETE[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func GET[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func HEAD[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func OPTIONS[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func PATCH[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func POST[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- func PUT[Req, Res any](r *Router, path string, handler Handler[Req, Res])
- type Box
- func (c *Box[Req, Res]) Accepted(data Res) error
- func (c *Box[Req, Res]) BadRequest(data Res) error
- func (c *Box[Req, Res]) Bind() error
- func (c *Box[Req, Res]) Created(location string, data Res) error
- func (c *Box[Req, Res]) Forbidden(data Res) error
- func (c *Box[Req, Res]) InternalServerError(data Res) error
- func (c *Box[Req, Res]) NoContentSuccess() error
- func (c *Box[Req, Res]) NotFound(data Res) error
- func (c *Box[Req, Res]) OK(data Res) error
- func (c *Box[Req, Res]) Unauthorized(data Res) error
- func (c *Box[Req, Res]) UpdatedNoContent() error
- func (c *Box[Req, Res]) UpdatedOK(data Res) error
- type Components
- type Contact
- type Context
- func (c *Context) APIVersion() Version
- func (c *Context) Abort()
- func (c *Context) Accepted(obj any) error
- func (c *Context) Accepts(mediaType string) bool
- func (c *Context) AcceptsAny(mediaTypes ...string) string
- func (c *Context) Blob(code int, contentType string, data []byte) error
- func (c *Context) Created(obj any) error
- func (c *Context) DB() any
- func (c *Context) Form(name string) string
- func (c *Context) FormDefault(name, defaultValue string) string
- func (c *Context) Get(key string) any
- func (c *Context) GetBool(key string) bool
- func (c *Context) GetHeader(key string) string
- func (c *Context) GetInt(key string) int
- func (c *Context) GetString(key string) string
- func (c *Context) IsAborted() bool
- func (c *Context) JSON(code int, obj any) error
- func (c *Context) JSONIndent(code int, obj any, indent string) error
- func (c *Context) Markdown(content string) error
- func (c *Context) Negotiate(status int, data any) error
- func (c *Context) NegotiateFormat(offered ...string) string
- func (c *Context) Next() error
- func (c *Context) NoContent(code int) error
- func (c *Context) NoContentSuccess() error
- func (c *Context) OK(obj any) error
- func (c *Context) Param(name string) string
- func (c *Context) PostForm(name string) string
- func (c *Context) Problem(p Problem) error
- func (c *Context) Query(name string) string
- func (c *Context) QueryDefault(name, defaultValue string) string
- func (c *Context) QueryValues(name string) []string
- func (c *Context) Redirect(code int, url string) error
- func (c *Context) Router() *Router
- func (c *Context) SSE(handler func(conn any) error) error
- func (c *Context) Set(key string, value any)
- func (c *Context) SetHeader(key, value string)
- func (c *Context) Stream(code int, contentType string, r io.Reader) error
- func (c *Context) String(code int, s string) error
- func (c *Context) Text(s string) error
- func (c *Context) WebSocket(handler func(conn any) error, opts any) error
- func (c *Context) XML(code int, obj any) error
- type DeprecationInfo
- type Empty
- type Example
- type Handler
- type HandlerFunc
- type Header
- type Info
- type License
- type MediaType
- type Middleware
- type OAuthFlow
- type OAuthFlows
- type OpenAPI
- type Operation
- type Param
- type Parameter
- type PathItem
- type Problem
- func BadRequest(detail string) Problem
- func Conflict(detail string) Problem
- func Forbidden(detail string) Problem
- func InternalServerError(detail string) Problem
- func MethodNotAllowed(detail string) Problem
- func NewProblem(status int, title, detail string) Problem
- func NotAcceptable(detail string) Problem
- func NotFound(detail string) Problem
- func ServiceUnavailable(detail string) Problem
- func TooManyRequests(detail string) Problem
- func Unauthorized(detail string) Problem
- func UnprocessableEntity(detail string) Problem
- func ValidationProblem(errs ValidationErrors) Problem
- func (p Problem) Error() string
- func (p Problem) MarshalJSON() ([]byte, error)
- func (p Problem) WithExtension(key string, value any) Problem
- func (p Problem) WithExtensions(extensions map[string]any) Problem
- func (p Problem) WithInstance(instance string) Problem
- func (p Problem) WithType(typeURI string) Problem
- type RequestBody
- type Response
- type RouteGroup
- func (g *RouteGroup) DELETE(path string, handler HandlerFunc)
- func (g *RouteGroup) GET(path string, handler HandlerFunc)
- func (g *RouteGroup) Group(prefix string, middleware ...HandlerFunc) *RouteGroup
- func (g *RouteGroup) HEAD(path string, handler HandlerFunc)
- func (g *RouteGroup) Handle(method, path string, handler HandlerFunc)
- func (g *RouteGroup) OPTIONS(path string, handler HandlerFunc)
- func (g *RouteGroup) PATCH(path string, handler HandlerFunc)
- func (g *RouteGroup) POST(path string, handler HandlerFunc)
- func (g *RouteGroup) PUT(path string, handler HandlerFunc)
- func (g *RouteGroup) Use(middleware ...HandlerFunc) *RouteGroup
- type RouteInfo
- type RouteOptions
- type RouteParameter
- type RouteResponse
- type Router
- func (r *Router) DELETE(path string, handler HandlerFunc)
- func (r *Router) GET(path string, handler HandlerFunc)
- func (r *Router) GenerateOpenAPI(info Info) (*OpenAPI, error)
- func (r *Router) Group(prefix string, middleware ...HandlerFunc) *RouteGroup
- func (r *Router) HEAD(path string, handler HandlerFunc)
- func (r *Router) Handle(method, path string, handler HandlerFunc)
- func (r *Router) HandleWithOptions(method, path string, handler HandlerFunc, opts *RouteOptions)
- func (r *Router) ListenAndServeWithShutdown(addr string, timeout ...time.Duration) error
- func (r *Router) OPTIONS(path string, handler HandlerFunc)
- func (r *Router) OnShutdown(f func())
- func (r *Router) PATCH(path string, handler HandlerFunc)
- func (r *Router) POST(path string, handler HandlerFunc)
- func (r *Router) PUT(path string, handler HandlerFunc)
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) ServeOpenAPI(path string)
- func (r *Router) SetServer(srv *http.Server)
- func (r *Router) SetValidator(v Validator) *Router
- func (r *Router) Shutdown(ctx context.Context) error
- func (r *Router) Use(middleware ...HandlerFunc) *Router
- func (r *Router) WithInfo(info Info) *Router
- func (r *Router) WithServer(server Server) *Router
- type Schema
- type SecurityRequirement
- type SecurityScheme
- type Server
- type ServerVariable
- type Tag
- type ValidationError
- type ValidationErrors
- type Validator
- type Version
Constants ¶
const ( MIMEApplicationJSON = "application/json" MIMETextHTML = "text/html" MIMEApplicationXML = "application/xml" MIMETextXML = "text/xml" MIMETextPlain = "text/plain" MIMETextMarkdown = "text/markdown" // Added for AI agents and documentation MIMEApplicationForm = "application/x-www-form-urlencoded" MIMEMultipartForm = "multipart/form-data" MIMEApplicationXYAML = "application/x-yaml" MIMEApplicationYAML = "application/yaml" MIMEApplicationTOML = "application/toml" )
MIME type constants for common content types.
Variables ¶
var ( ErrUnauthorized = errors.New("unauthorized") // ErrForbidden is returned when user is authenticated but not authorized. ErrForbidden = errors.New("forbidden") // ErrNotFound is returned when a resource is not found. ErrNotFound = errors.New("not found") // ErrBadRequest is returned when the request is invalid. ErrBadRequest = errors.New("bad request") // ErrInternalServerError is returned for server errors. ErrInternalServerError = errors.New("internal server error") )
Common HTTP errors.
var ErrInvalidRedirectCode = errors.New("fursy: invalid redirect code (must be 3xx)")
ErrInvalidRedirectCode is returned when redirect code is not 3xx.
var ErrStreamNotImported = errors.New("fursy: stream plugin not imported - add 'import _ \"github.com/coregx/fursy/plugins/stream\"' to your code")
ErrStreamNotImported is returned when SSE or WebSocket methods are called without importing github.com/coregx/fursy/plugins/stream package.
Functions ¶
func DELETE ¶
DELETE registers a type-safe handler for DELETE requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body.
Example:
fursy.DELETE[fursy.Empty, fursy.Empty](router, "/users/:id", func(c *fursy.Box[fursy.Empty, fursy.Empty]) error {
id := c.Param("id")
db.DeleteUser(id)
return c.NoContent(204)
})
func GET ¶
GET registers a type-safe handler for GET requests to the specified path.
Since Go doesn't support generic methods, we use top-level functions instead. This provides clean type-safe routing with automatic request binding.
Type parameters:
- Req: The expected request body type (use Empty for GET requests)
- Res: The response body type
Example:
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
}
fursy.GET[fursy.Empty, UserResponse](router, "/users/:id", func(c *fursy.Box[fursy.Empty, UserResponse]) error {
id := c.Param("id")
user := db.GetUser(id)
return c.OK(UserResponse{ID: user.ID, Name: user.Name})
})
func HEAD ¶
HEAD registers a type-safe handler for HEAD requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body. HEAD requests should not return a body, only headers.
Example:
fursy.HEAD[fursy.Empty, fursy.Empty](router, "/users/:id", func(c *fursy.Box[fursy.Empty, fursy.Empty]) error {
id := c.Param("id")
if db.UserExists(id) {
return c.NoContent(200)
}
return c.NoContent(404)
})
func OPTIONS ¶
OPTIONS registers a type-safe handler for OPTIONS requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body. OPTIONS requests are typically used for CORS preflight checks.
Example:
fursy.OPTIONS[fursy.Empty, fursy.Empty](router, "/users", func(c *fursy.Box[fursy.Empty, fursy.Empty]) error {
c.SetHeader("Allow", "GET, POST, PUT, DELETE")
c.SetHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
return c.NoContent(200)
})
func PATCH ¶
PATCH registers a type-safe handler for PATCH requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body.
Example:
type PatchUserRequest struct {
Name *string `json:"name,omitempty"`
Email *string `json:"email,omitempty"`
}
fursy.PATCH[PatchUserRequest, UserResponse](router, "/users/:id", func(c *fursy.Box[PatchUserRequest, UserResponse]) error {
id := c.Param("id")
req := c.ReqBody
user := db.PatchUser(id, req)
return c.OK(UserResponse{ID: user.ID, Name: user.Name})
})
func POST ¶
POST registers a type-safe handler for POST requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body.
Example:
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
}
fursy.POST[CreateUserRequest, UserResponse](router, "/users", func(c *fursy.Box[CreateUserRequest, UserResponse]) error {
req := c.ReqBody
user := db.CreateUser(req.Name, req.Email)
return c.Created("/users/"+user.ID, UserResponse{ID: user.ID, Name: user.Name})
})
func PUT ¶
PUT registers a type-safe handler for PUT requests to the specified path.
The handler will receive a Box[Req, Res] with automatically bound request body.
Example:
type UpdateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
fursy.PUT[UpdateUserRequest, UserResponse](router, "/users/:id", func(c *fursy.Box[UpdateUserRequest, UserResponse]) error {
id := c.Param("id")
req := c.ReqBody
user := db.UpdateUser(id, req.Name, req.Email)
return c.OK(UserResponse{ID: user.ID, Name: user.Name})
})
Types ¶
type Box ¶
type Box[Req, Res any] struct { *Context // ReqBody is the parsed and validated request body. // It is automatically bound from the request based on Content-Type. // For handlers with no request body, use Empty type and ReqBody will be nil. ReqBody *Req // ResBody is the response body to be sent. // Set this field or use type-safe response methods (OK, Created, etc.) // For handlers with no response body, use Empty type. ResBody *Res }
Box is a type-safe context for HTTP request handling with request/response types.
It embeds *Context, providing all base functionality (params, middleware, etc.), while adding type-safe request and response body handling.
Type parameters:
- Req: The expected request body type (use Empty if no body)
- Res: The response body type (use Empty if no structured response)
The context automatically binds the request body to ReqBody based on Content-Type. The response body (ResBody) can be set and sent using type-safe methods like OK(), Created(), etc.
Example:
type CreateUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
router.POST[CreateUserRequest, UserResponse]("/users", func(c *Box[CreateUserRequest, UserResponse]) error {
// ReqBody is automatically bound from JSON
req := c.ReqBody
// Create user
user := db.CreateUser(req.Name, req.Email)
// Set response
return c.Created("/users/"+user.ID, UserResponse{
ID: user.ID,
Name: user.Name,
Email: user.Email,
})
})
func (*Box[Req, Res]) Accepted ¶
Accepted sends a 202 Accepted response with data.
Use this when a request has been accepted for processing but the processing has not been completed yet (e.g., async operations).
Example:
return c.Accepted(TaskResponse{TaskID: "abc123", Status: "pending"})
func (*Box[Req, Res]) BadRequest ¶
BadRequest sends a 400 Bad Request response with error details.
Example:
return c.BadRequest(ErrorResponse{Message: "Invalid email format"})
func (*Box[Req, Res]) Bind ¶
Bind binds the request body to ReqBody based on Content-Type.
Supported content types:
- application/json (default)
- application/xml, text/xml
- application/x-www-form-urlencoded
- multipart/form-data
If a validator is set via Router.SetValidator(), the request body will be automatically validated after binding. Validation errors are returned as ValidationErrors.
This method is automatically called by the generic handler adapter, so you typically don't need to call it manually.
Returns error if binding or validation fails.
Example:
// Manual binding (usually automatic)
if err := c.Bind(); err != nil {
return c.BadRequest(ErrorResponse{Message: err.Error()})
}
func (*Box[Req, Res]) Created ¶
Created sends a 201 Created response with Location header and data.
The location parameter should be the URL of the newly created resource.
Example:
return c.Created("/users/123", UserResponse{ID: 123, Name: "John"})
func (*Box[Req, Res]) Forbidden ¶
Forbidden sends a 403 Forbidden response with error details.
Example:
return c.Forbidden(ErrorResponse{Message: "Insufficient permissions"})
func (*Box[Req, Res]) InternalServerError ¶
InternalServerError sends a 500 Internal Server Error response with error details.
Example:
return c.InternalServerError(ErrorResponse{Message: "Database connection failed"})
func (*Box[Req, Res]) NoContentSuccess ¶
NoContentSuccess sends a 204 No Content response. This is a convenience method for successful operations with no response body.
Common for DELETE operations and some PUT/PATCH updates. REST best practice: DELETE should return 204, not 200.
Example:
router.DELETE[DeleteUserRequest, Empty]("/users/:id", func(c *Box[DeleteUserRequest, Empty]) error {
deleteUser(c.ReqBody.ID)
return c.NoContentSuccess() // 204 No Content
})
func (*Box[Req, Res]) NotFound ¶
NotFound sends a 404 Not Found response with error details.
Example:
return c.NotFound(ErrorResponse{Message: "User not found"})
func (*Box[Req, Res]) OK ¶
OK sends a 200 OK response with the given data.
Example:
return c.OK(UserResponse{ID: 1, Name: "John"})
func (*Box[Req, Res]) Unauthorized ¶
Unauthorized sends a 401 Unauthorized response with error details.
Example:
return c.Unauthorized(ErrorResponse{Message: "Invalid credentials"})
func (*Box[Req, Res]) UpdatedNoContent ¶
UpdatedNoContent sends a 204 No Content response for successful updates without response body. Use this when PUT/PATCH operations succeed but don't return data.
Example:
router.PUT[UpdateUserRequest, Empty]("/users/:id", func(c *Box[UpdateUserRequest, Empty]) error {
updateUser(c.ReqBody)
return c.UpdatedNoContent() // 204 No Content
})
func (*Box[Req, Res]) UpdatedOK ¶
UpdatedOK sends a 200 OK response for successful updates with response body. This is an alias for OK() but provides semantic clarity for PUT/PATCH operations.
Example:
router.PUT[UpdateUserRequest, UserResponse]("/users/:id", func(c *Box[UpdateUserRequest, UserResponse]) error {
updated := updateUser(c.ReqBody)
return c.UpdatedOK(updated) // 200 OK - semantically clear it's an update
})
type Components ¶
type Components struct {
// Schemas is a map of reusable Schema objects.
Schemas map[string]*Schema `json:"schemas,omitempty"`
// Responses is a map of reusable Response objects.
Responses map[string]Response `json:"responses,omitempty"`
// Parameters is a map of reusable Parameter objects.
Parameters map[string]Parameter `json:"parameters,omitempty"`
// RequestBodies is a map of reusable RequestBody objects.
RequestBodies map[string]RequestBody `json:"requestBodies,omitempty"`
// Headers is a map of reusable Header objects.
Headers map[string]Header `json:"headers,omitempty"`
// SecuritySchemes is a map of reusable SecurityScheme objects.
SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty"`
}
Components holds reusable objects.
type Contact ¶
type Contact struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Email string `json:"email,omitempty"`
}
Contact information for the exposed API.
type Context ¶
type Context struct {
// Request is the current HTTP request.
Request *http.Request
// Response is the response writer.
Response http.ResponseWriter
// contains filtered or unexported fields
}
Context is the base context for all handlers and middleware. It provides access to request/response, routing info, and middleware chain execution.
Context is designed to be embedded in higher-level context types (like generic Box[Req, Res]) while providing all the core functionality needed for middleware and handlers.
The context is pooled and reused across requests for zero allocations. Do not store Context references - all operations must complete within the handler's execution.
func (*Context) APIVersion ¶
APIVersion returns the current API version from the request.
Version is extracted in this order:
- Api-Version header
- URL path (/v1/, /v2/, etc.)
Returns zero Version if no version found.
Example:
version := c.APIVersion()
if version.Major == 1 {
// Handle v1 request
}
func (*Context) Abort ¶
func (c *Context) Abort()
Abort prevents pending handlers from being called. Note that this does not stop the current handler - it only prevents subsequent handlers in the chain.
This is useful when a middleware wants to stop the chain (e.g., authentication failure).
Example:
func RequireAuth() HandlerFunc {
return func(c *Context) error {
if !isAuthenticated(c) {
c.Abort()
return c.JSON(401, map[string]string{"error": "unauthorized"})
}
return c.Next()
}
}
func (*Context) Accepted ¶
Accepted sends a 202 Accepted JSON response. Use this when the request has been accepted for processing but not completed.
Common for async operations, background jobs, or queued tasks.
Example:
router.POST("/jobs", func(c *fursy.Context) error {
jobID := startAsyncJob(c)
return c.Accepted(map[string]string{"jobId": jobID}) // 202 Accepted
})
func (*Context) Accepts ¶ added in v0.2.0
Accepts returns true if the specified media type is acceptable based on the request's Accept header.
This is a convenience wrapper around NegotiateFormat for simple cases where you want to check if a specific media type is acceptable.
Example:
if c.Accepts(MIMETextMarkdown) {
return c.Markdown(renderMarkdown(docs))
}
return c.JSON(200, data)
func (*Context) AcceptsAny ¶ added in v0.2.0
AcceptsAny returns the best matching media type from the provided options based on the request's Accept header and quality values (q-values).
This is an alias for NegotiateFormat with a more intuitive name for checking multiple media types. Returns empty string if none of the offered types are acceptable.
Example:
switch c.AcceptsAny(MIMETextMarkdown, MIMETextHTML, MIMEApplicationJSON) {
case MIMETextMarkdown:
return c.Markdown(renderMarkdown(data))
case MIMETextHTML:
return c.HTML(200, renderHTML(data))
default:
return c.JSON(200, data)
}
func (*Context) Blob ¶
Blob sends a binary response with custom content type. This is useful for sending raw binary data like images or files.
Example:
imageData := []byte{...}
return c.Blob(200, "image/png", imageData)
func (*Context) Created ¶
Created sends a 201 Created JSON response. Use this for successful POST requests that create a new resource.
REST best practice: POST operations that create resources should return 201, not 200.
Example:
router.POST("/users", func(c *fursy.Context) error {
newUser := createUser(c)
return c.Created(newUser) // 201 Created
})
func (*Context) DB ¶ added in v0.3.0
DB returns the database connection from the context.
Returns nil if database middleware is not configured.
Requires: github.com/coregx/fursy/plugins/database package
Example:
// In main.go:
import (
"database/sql"
"github.com/coregx/fursy"
"github.com/coregx/fursy/plugins/database"
_ "github.com/lib/pq" // PostgreSQL driver
)
sqlDB, _ := sql.Open("postgres", dsn)
db := database.NewDB(sqlDB)
router := fursy.New()
router.Use(database.Middleware(db))
router.GET("/users/:id", func(c *fursy.Context) error {
db := c.DB()
if db == nil {
return c.Problem(fursy.InternalServerError("Database not configured"))
}
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"))
}
if err != nil {
return c.Problem(fursy.InternalServerError(err.Error()))
}
return c.JSON(200, user)
})
Note: This method signature returns 'any' to avoid importing github.com/coregx/fursy/plugins/database in fursy core. The actual type is *database.DB when database middleware is configured.
func (*Context) Form ¶
Form returns the first value for the named form parameter. It checks both POST/PUT body parameters and URL query parameters. Form parameters take precedence over query parameters.
For multipart forms, it parses up to 32MB of data.
Example:
// POST /login with body: username=john&password=secret
username := c.Form("username") // "john"
func (*Context) FormDefault ¶
FormDefault returns the form parameter value or a default value. If the parameter doesn't exist or is empty, returns defaultValue.
Example:
role := c.FormDefault("role", "user") // "user" if not provided
func (*Context) Get ¶
Get retrieves data from the context. Returns nil if the key doesn't exist.
This is useful for passing data between middleware and handlers.
Example:
// In authentication middleware:
c.Set("userID", "123")
// In handler:
userID := c.Get("userID").(string)
func (*Context) GetBool ¶
GetBool retrieves a bool value from the context. Returns false if the key doesn't exist or value is not a bool.
Example:
authenticated := c.GetBool("authenticated")
func (*Context) GetHeader ¶
GetHeader returns a request header value. Returns empty string if the header doesn't exist.
Example:
userAgent := c.GetHeader("User-Agent")
func (*Context) GetInt ¶
GetInt retrieves an int value from the context. Returns 0 if the key doesn't exist or value is not an int.
Example:
page := c.GetInt("page")
func (*Context) GetString ¶
GetString retrieves a string value from the context. Returns empty string if the key doesn't exist or value is not a string.
Example:
userID := c.GetString("userID")
func (*Context) IsAborted ¶
IsAborted returns true if the context was aborted. This can be used to check if a middleware called Abort().
func (*Context) JSON ¶
JSON sends a JSON response. The obj is encoded using encoding/json and sent with application/json content type.
Example:
return c.JSON(200, map[string]string{"message": "success"})
func (*Context) JSONIndent ¶
JSONIndent sends a JSON response with indentation for pretty-printing. This is useful for debugging or human-readable responses.
Example:
return c.JSONIndent(200, data, " ") // 2-space indent
func (*Context) Markdown ¶ added in v0.2.0
Markdown sends a markdown text response with status 200. Sets Content-Type to "text/markdown; charset=utf-8".
This is a convenience method for serving markdown content, particularly useful for AI agents and documentation endpoints.
Example:
router.GET("/docs.md", func(c *Context) error {
md := `# API Documentation
## Endpoints
- GET /users - List all users
- POST /users - Create new user`
return c.Markdown(md)
})
func (*Context) Negotiate ¶
Negotiate performs content negotiation and sends the response in the best format.
This is a convenience method that combines NegotiateFormat with automatic response rendering. It automatically sets the Vary: Accept header for proper HTTP caching.
Supported formats:
- application/json (JSON)
- application/xml, text/xml (XML)
- text/html (HTML - requires HTMLData and HTMLTemplate)
- text/plain (Plain text)
Returns ErrNotAcceptable if no acceptable format is found.
Example:
type User struct {
ID int `json:"id" xml:"id"`
Name string `json:"name" xml:"name"`
}
user := User{ID: 1, Name: "John"}
return c.Negotiate(200, user)
// Client with "Accept: application/json" receives JSON
// Client with "Accept: application/xml" receives XML
func (*Context) NegotiateFormat ¶
NegotiateFormat returns the best offered content type based on the Accept header.
This method performs RFC 9110 compliant content negotiation, including:
- Quality value (q) weighting
- Specificity matching (explicit > wildcard)
- Parameter precedence
Returns the selected content type, or an empty string if no match found.
Example:
format := c.NegotiateFormat(fursy.MIMEApplicationJSON, fursy.MIMETextHTML, fursy.MIMEApplicationXML)
switch format {
case fursy.MIMEApplicationJSON:
return c.JSON(200, data)
case fursy.MIMETextHTML:
return c.HTML(200, "template", data)
case fursy.MIMEApplicationXML:
return c.XML(200, data)
default:
return c.Problem(NotAcceptable("No acceptable format found"))
}
func (*Context) Next ¶
Next executes the next handler in the middleware chain. It returns the error from the handler, allowing middleware to handle or transform errors.
Example middleware:
func Logger() HandlerFunc {
return func(c *Context) error {
start := time.Now()
err := c.Next() // Call next handler
log.Printf("%s - %v", c.Request.URL.Path, time.Since(start))
return err
}
}
func (*Context) NoContent ¶
NoContent sends a response with no body. This is commonly used for 204 No Content responses.
Example:
return c.NoContent(204) // Successful deletion
func (*Context) NoContentSuccess ¶
NoContentSuccess sends a 204 No Content response. This is a convenience method for successful operations with no response body.
Common for DELETE operations and some PUT/PATCH updates. REST best practice: DELETE should return 204, not 200.
Example:
router.DELETE("/users/:id", func(c *fursy.Context) error {
deleteUser(c.Param("id"))
return c.NoContentSuccess() // 204 No Content
})
func (*Context) OK ¶
OK sends a 200 OK JSON response. This is a convenience method for the most common success case.
Use this for successful GET requests or operations that return data.
Example:
router.GET("/users", func(c *fursy.Context) error {
users := getAllUsers()
return c.OK(users) // 200 OK
})
func (*Context) Param ¶
Param returns the value of the URL parameter by name. Returns empty string if the parameter doesn't exist.
Example:
// Route: /users/:id
// Request: /users/123
id := c.Param("id") // "123"
func (*Context) PostForm ¶
PostForm returns the form value from POST/PUT body only (not URL query). Unlike Form(), this does not fall back to query parameters.
Example:
// POST /update?id=123 with body: name=john
name := c.PostForm("name") // "john"
id := c.PostForm("id") // "" (not in POST body)
func (*Context) Problem ¶
Problem sends an RFC 9457 Problem Details response.
Problem Details (RFC 9457) provides a standard way to carry machine-readable details of errors in HTTP responses, with Content-Type: application/problem+json.
Example:
return c.Problem(fursy.NotFound("User not found"))
return c.Problem(fursy.UnprocessableEntity("Invalid input").
WithExtension("field", "email").
WithExtension("reason", "already exists"))
For validation errors, use ValidationProblem:
if err := c.Bind(); err != nil {
if verr, ok := err.(ValidationErrors); ok {
return c.Problem(ValidationProblem(verr))
}
return c.Problem(BadRequest(err.Error()))
}
func (*Context) Query ¶
Query returns the first value for the named query parameter. Returns empty string if the parameter doesn't exist.
The query string is parsed lazily on first access and cached for subsequent calls.
Example:
// Request: /users?page=2&limit=10
page := c.Query("page") // "2"
limit := c.Query("limit") // "10"
func (*Context) QueryDefault ¶
QueryDefault returns the query parameter value or a default value. If the parameter doesn't exist or is empty, returns defaultValue.
Example:
// Request: /users?page=2
page := c.QueryDefault("page", "1") // "2"
limit := c.QueryDefault("limit", "10") // "10" (default)
func (*Context) QueryValues ¶
QueryValues returns all values for the named query parameter. Returns nil if the parameter doesn't exist.
Example:
// Request: /search?tag=go&tag=web&tag=api
tags := c.QueryValues("tag") // []string{"go", "web", "api"}
func (*Context) Redirect ¶
Redirect sends an HTTP redirect response. The code must be in the 3xx range (300-308).
Common redirect codes:
- 301: Moved Permanently
- 302: Found (temporary redirect)
- 303: See Other
- 307: Temporary Redirect (preserves method)
- 308: Permanent Redirect (preserves method)
Example:
return c.Redirect(302, "/login")
func (*Context) Router ¶
Router returns the router instance that is handling this request. This can be used to access router configuration or state.
func (*Context) SSE ¶ added in v0.3.0
SSE upgrades the HTTP connection to Server-Sent Events.
The handler function receives an SSE connection and should handle the SSE lifecycle (register to hub, send events, etc.).
The connection is automatically closed when the handler returns.
Requires: github.com/coregx/stream/sse package
Example:
// In main.go:
import (
"github.com/coregx/fursy"
"github.com/coregx/fursy/plugins/stream"
"github.com/coregx/stream/sse"
)
hub := sse.NewHub[Notification]()
go hub.Run()
defer hub.Close()
router := fursy.New()
router.Use(stream.SSEHub(hub))
router.GET("/events", func(c *fursy.Context) error {
hub, _ := stream.GetSSEHub[Notification](c)
return c.SSE(func(conn *sse.Conn) error {
hub.Register(conn)
defer hub.Unregister(conn)
<-conn.Done()
return nil
})
})
Note: This method signature is defined in fursy core, but requires github.com/coregx/stream/sse to be imported in your code for the Conn type.
func (*Context) Set ¶
Set stores data in the context. This is useful for passing data between middleware and handlers.
Example:
c.Set("userID", "123")
c.Set("authenticated", true)
func (*Context) SetHeader ¶
SetHeader sets a response header. This must be called before writing the response body.
Example:
c.SetHeader("X-Request-ID", requestID)
return c.String(200, "OK")
func (*Context) Stream ¶
Stream sends a response from an io.Reader. This is useful for streaming large files or data without loading everything into memory.
Example:
file, _ := os.Open("large-file.pdf")
defer file.Close()
return c.Stream(200, "application/pdf", file)
func (*Context) String ¶
String sends a plain text response.
Example:
return c.String(200, "Hello, World!")
func (*Context) Text ¶
Text sends a 200 OK plain text response. This is a convenience method for simple text responses.
Example:
router.GET("/ping", func(c *fursy.Context) error {
return c.Text("pong") // 200 OK, text/plain
})
func (*Context) WebSocket ¶ added in v0.3.0
WebSocket upgrades the HTTP connection to WebSocket.
The handler function receives a WebSocket connection and should handle the WebSocket lifecycle (register to hub, read/write messages, etc.).
The connection is automatically closed when the handler returns.
Requires: github.com/coregx/stream/websocket package
Example:
// In main.go:
import (
"github.com/coregx/fursy"
"github.com/coregx/fursy/plugins/stream"
"github.com/coregx/stream/websocket"
)
hub := websocket.NewHub()
go hub.Run()
defer hub.Close()
router := fursy.New()
router.Use(stream.WebSocketHub(hub))
router.GET("/ws", func(c *fursy.Context) error {
hub, _ := stream.GetWebSocketHub(c)
return c.WebSocket(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)
}
}, nil)
})
Note: This method signature is defined in fursy core, but requires github.com/coregx/stream/websocket to be imported in your code for the Conn type.
type DeprecationInfo ¶
type DeprecationInfo struct {
// Version that is deprecated.
Version Version
// SunsetDate is when this version will be removed (RFC 8594).
SunsetDate *time.Time
// Message is a human-readable deprecation message.
Message string
// Link to migration guide or new version docs.
Link string
}
DeprecationInfo contains information about API deprecation.
RFC 8594 defines the Sunset HTTP header to communicate deprecation.
func (*DeprecationInfo) SetDeprecationHeaders ¶
func (d *DeprecationInfo) SetDeprecationHeaders(c *Context)
SetDeprecationHeaders sets deprecation headers on the response.
RFC 8594 Sunset Header:
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Also sets:
type Empty ¶
type Empty struct{}
Empty represents the absence of a request or response body in type-safe handlers.
Use Empty when a handler doesn't need to bind a request body or send a response body. This maintains type safety while indicating "no data" intent.
Examples:
// Handler with no request body, returns string
router.GET[Empty, string]("/hello", func(c *Box[Empty, string]) error {
c.ResBody = new(string)
*c.ResBody = "Hello, World!"
return c.OK(*c.ResBody)
})
// Handler with request body, no response body
router.POST[CreateUserRequest, Empty]("/users", func(c *Box[CreateUserRequest, Empty]) error {
user := c.ReqBody
db.CreateUser(user)
return c.NoContent(201)
})
// Handler with neither request nor response body
router.DELETE[Empty, Empty]("/cache", func(c *Box[Empty, Empty]) error {
cache.Clear()
return c.NoContent(204)
})
type Example ¶
type Example struct {
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
Value any `json:"value,omitempty"`
ExternalValue string `json:"externalValue,omitempty"`
}
Example represents an example value.
type Handler ¶
Handler is a type-safe handler function for HTTP requests with typed request/response bodies.
Type parameters:
- Req: The expected request body type (use Empty if no body)
- Res: The response body type (use Empty if no structured response)
The handler receives a Box[Req, Res] with automatically bound request body (ReqBody) and provides type-safe methods for sending responses.
Example:
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type UserResponse struct {
ID int `json:"id"`
Name string `json:"name"`
}
func createUser(c *Box[CreateUserRequest, UserResponse]) error {
req := c.ReqBody // Type: *CreateUserRequest
user := db.Create(req.Name, req.Email)
return c.Created("/users/"+user.ID, UserResponse{
ID: user.ID,
Name: user.Name,
})
}
router.POST[CreateUserRequest, UserResponse]("/users", createUser)
type HandlerFunc ¶
HandlerFunc is the function signature for simple non-generic handlers and middleware. It receives a Context and returns an error.
Example handler:
func GetUser(c *Context) error {
id := c.Param("id")
return c.JSON(200, map[string]string{"id": id})
}
Example middleware:
func Logger() HandlerFunc {
return func(c *Context) error {
start := time.Now()
err := c.Next() // Call next handler
log.Printf("%s %s - %v", c.Request.Method, c.Request.URL.Path, time.Since(start))
return err
}
}
For type-safe generic handlers with automatic request/response binding, use Handler[Req, Res] instead, which receives Box[Req, Res].
func DeprecateVersion ¶
func DeprecateVersion(info DeprecationInfo) HandlerFunc
DeprecateVersion is a middleware that marks a version as deprecated.
Adds deprecation headers to all responses for this version.
Example:
v1 := router.Group("/api/v1")
v1.Use(fursy.DeprecateVersion(fursy.DeprecationInfo{
Version: fursy.Version{Major: 1},
SunsetDate: &sunsetDate,
Message: "Please migrate to v2",
Link: "https://api.example.com/docs/v2-migration",
}))
func RequireVersion ¶
func RequireVersion(required Version) HandlerFunc
RequireVersion is a middleware that requires a specific API version.
If the request version doesn't match, returns 404 Not Found or 400 Bad Request.
Example:
v1 := router.Group("/api/v1")
v1.Use(fursy.RequireVersion(fursy.Version{Major: 1}))
v2 := router.Group("/api/v2")
v2.Use(fursy.RequireVersion(fursy.Version{Major: 2}))
type Header ¶
type Header struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
Schema *Schema `json:"schema,omitempty"`
}
Header represents a header parameter.
type Info ¶
type Info struct {
// Title of the API (required).
Title string `json:"title"`
// Version of the OpenAPI document (required).
Version string `json:"version"`
// Summary is a short summary of the API.
Summary string `json:"summary,omitempty"`
// Description is a description of the API (supports CommonMark).
Description string `json:"description,omitempty"`
// Contact information for the exposed API.
Contact *Contact `json:"contact,omitempty"`
// License information for the exposed API.
License *License `json:"license,omitempty"`
// TermsOfService is a URL to the Terms of Service for the API.
TermsOfService string `json:"termsOfService,omitempty"`
}
Info provides metadata about the API.
type License ¶
type License struct {
Name string `json:"name"`
Identifier string `json:"identifier,omitempty"`
URL string `json:"url,omitempty"`
}
License information for the exposed API.
type MediaType ¶
type MediaType struct {
// Schema defining the content of the request, response, or parameter.
Schema *Schema `json:"schema,omitempty"`
// Example of the media type.
Example any `json:"example,omitempty"`
// Examples of the media type.
Examples map[string]Example `json:"examples,omitempty"`
}
MediaType provides schema and examples for the media type.
type Middleware ¶
type Middleware func(HandlerFunc) HandlerFunc
Middleware is a function that wraps a HandlerFunc. This is an optional pattern - middleware can also be written as HandlerFunc directly.
Example:
func MyMiddleware() Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(c *Context) error {
// Before handler
err := next(c)
// After handler
return err
}
}
}
type OAuthFlow ¶
type OAuthFlow struct {
AuthorizationURL string `json:"authorizationUrl,omitempty"`
TokenURL string `json:"tokenUrl,omitempty"`
RefreshURL string `json:"refreshUrl,omitempty"`
Scopes map[string]string `json:"scopes"`
}
OAuthFlow represents an OAuth flow.
type OAuthFlows ¶
type OAuthFlows struct {
Implicit *OAuthFlow `json:"implicit,omitempty"`
Password *OAuthFlow `json:"password,omitempty"`
ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty"`
AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty"`
}
OAuthFlows represents OAuth flows.
type OpenAPI ¶
type OpenAPI struct {
// OpenAPI version string (e.g., "3.1.0").
OpenAPI string `json:"openapi"`
// Info provides metadata about the API.
Info Info `json:"info"`
// Servers is an array of Server objects providing connectivity information.
Servers []Server `json:"servers,omitempty"`
// Paths holds the available paths and operations for the API.
Paths map[string]PathItem `json:"paths"`
// Components holds reusable objects for different aspects of the OAS.
Components *Components `json:"components,omitempty"`
// Security is a declaration of which security mechanisms can be used across the API.
Security []SecurityRequirement `json:"security,omitempty"`
// Tags is a list of tags used by the document with additional metadata.
Tags []Tag `json:"tags,omitempty"`
}
OpenAPI represents an OpenAPI 3.1 document.
This is the root object of the OpenAPI Description. It provides metadata about the API and describes the available endpoints.
type Operation ¶
type Operation struct {
// Tags for API documentation control.
Tags []string `json:"tags,omitempty"`
// Summary is a short summary of what the operation does.
Summary string `json:"summary,omitempty"`
// Description is a verbose explanation of the operation behavior.
Description string `json:"description,omitempty"`
// OperationID is a unique string used to identify the operation.
OperationID string `json:"operationId,omitempty"`
// Parameters that are applicable for this operation.
Parameters []Parameter `json:"parameters,omitempty"`
// RequestBody applicable for this operation.
RequestBody *RequestBody `json:"requestBody,omitempty"`
// Responses is the list of possible responses.
Responses map[string]Response `json:"responses"`
// Deprecated declares this operation to be deprecated.
Deprecated bool `json:"deprecated,omitempty"`
// Security is a declaration of which security mechanisms can be used.
Security []SecurityRequirement `json:"security,omitempty"`
}
Operation describes a single API operation on a path.
type Param ¶
type Param struct {
Key string // Parameter name (e.g., "id" from /:id)
Value string // Parameter value extracted from path
}
Param represents a URL parameter extracted from the path.
Example:
For route "/users/:id" and path "/users/123":
Param{Key: "id", Value: "123"}
type Parameter ¶
type Parameter struct {
// Name of the parameter (required).
Name string `json:"name"`
// In is the location of the parameter (required).
// Possible values: "query", "header", "path", "cookie".
In string `json:"in"`
// Description of the parameter.
Description string `json:"description,omitempty"`
// Required determines whether this parameter is mandatory.
// Must be true if the parameter location is "path".
Required bool `json:"required,omitempty"`
// Deprecated specifies that a parameter is deprecated.
Deprecated bool `json:"deprecated,omitempty"`
// Schema defining the type used for the parameter.
Schema *Schema `json:"schema,omitempty"`
}
Parameter describes a single operation parameter.
type PathItem ¶
type PathItem struct {
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
Get *Operation `json:"get,omitempty"`
Post *Operation `json:"post,omitempty"`
Put *Operation `json:"put,omitempty"`
Delete *Operation `json:"delete,omitempty"`
Patch *Operation `json:"patch,omitempty"`
Head *Operation `json:"head,omitempty"`
Options *Operation `json:"options,omitempty"`
Parameters []Parameter `json:"parameters,omitempty"`
}
PathItem describes operations available on a single path.
type Problem ¶
type Problem struct {
// Type is a URI reference that identifies the problem type.
// It should provide human-readable documentation for the problem type.
// When dereferenced, it SHOULD provide human-readable documentation.
// Defaults to "about:blank" if not specified.
Type string `json:"type"`
// Title is a short, human-readable summary of the problem type.
// It SHOULD NOT change from occurrence to occurrence of the problem,
// except for purposes of localization.
Title string `json:"title"`
// Status is the HTTP status code generated by the origin server.
Status int `json:"status"`
// Detail is a human-readable explanation specific to this occurrence.
// It SHOULD focus on helping the client correct the problem.
Detail string `json:"detail,omitempty"`
// Instance is a URI reference that identifies the specific occurrence.
// It may or may not yield further information if dereferenced.
Instance string `json:"instance,omitempty"`
// Extensions contains additional problem-specific fields.
// These will be flattened into the JSON output alongside standard fields.
//
// Example:
// Extensions: map[string]any{
// "balance": 30,
// "cost": 50,
// }
Extensions map[string]any `json:"-"`
}
Problem represents an RFC 9457 Problem Details object.
RFC 9457 defines a "problem detail" as a way to carry machine-readable details of errors in HTTP response content to avoid the need to define new error response formats for HTTP APIs.
Standard fields:
- type: URI reference identifying the problem type
- title: Short, human-readable summary
- status: HTTP status code
- detail: Human-readable explanation specific to this occurrence
- instance: URI reference to the specific occurrence
Extensions can be added via the Extensions map, which will be flattened into the JSON output.
Example:
problem := fursy.Problem{
Type: "https://example.com/probs/out-of-credit",
Title: "You do not have enough credit",
Status: 403,
Detail: "Your current balance is 30, but that costs 50",
Instance: "/account/12345/msgs/abc",
}
Media Type: application/problem+json
Spec: https://www.rfc-editor.org/rfc/rfc9457.html
func BadRequest ¶
BadRequest creates a 400 Bad Request problem.
func InternalServerError ¶
InternalServerError creates a 500 Internal Server Error problem.
func MethodNotAllowed ¶
MethodNotAllowed creates a 405 Method Not Allowed problem.
func NewProblem ¶
NewProblem creates a new Problem with the given status, title, and detail. The type defaults to "about:blank" as per RFC 9457.
func NotAcceptable ¶
NotAcceptable creates a 406 Not Acceptable Problem.
RFC 9110 Section 15.5.7: The 406 Not Acceptable status code indicates that the target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation.
func ServiceUnavailable ¶
ServiceUnavailable creates a 503 Service Unavailable problem.
func TooManyRequests ¶
TooManyRequests creates a 429 Too Many Requests problem.
func Unauthorized ¶
Unauthorized creates a 401 Unauthorized problem.
func UnprocessableEntity ¶
UnprocessableEntity creates a 422 Unprocessable Entity problem. This is commonly used for validation errors.
func ValidationProblem ¶
func ValidationProblem(errs ValidationErrors) Problem
ValidationProblem creates a 422 Unprocessable Entity problem from ValidationErrors.
The validation errors are included as an extension field "errors" containing a map of field names to error messages.
Example output:
{
"type": "about:blank",
"title": "Validation Failed",
"status": 422,
"detail": "One or more fields failed validation",
"errors": {
"email": "must be a valid email address",
"age": "must be at least 18"
}
}
func (Problem) Error ¶
Error implements the error interface. Returns the detail if available, otherwise the title.
func (Problem) MarshalJSON ¶
MarshalJSON implements custom JSON marshaling to flatten extensions.
func (Problem) WithExtension ¶
WithExtension adds an extension field to the problem.
func (Problem) WithExtensions ¶
WithExtensions sets multiple extension fields at once.
func (Problem) WithInstance ¶
WithInstance sets the instance URI for the problem.
type RequestBody ¶
type RequestBody struct {
// Description of the request body.
Description string `json:"description,omitempty"`
// Content is a map of media types to media type objects.
Content map[string]MediaType `json:"content"`
// Required determines if the request body is required.
Required bool `json:"required,omitempty"`
}
RequestBody describes a single request body.
type Response ¶
type Response struct {
// Description of the response (required).
Description string `json:"description"`
// Content is a map of media types to media type objects.
Content map[string]MediaType `json:"content,omitempty"`
// Headers is a map of response headers.
Headers map[string]Header `json:"headers,omitempty"`
}
Response describes a single response from an API operation.
type RouteGroup ¶
type RouteGroup struct {
// contains filtered or unexported fields
}
RouteGroup represents a group of routes that share the same path prefix and middleware. Groups allow organizing routes hierarchically and applying middleware to specific route sets.
Example:
api := router.Group("/api")
api.Use(AuthMiddleware())
v1 := api.Group("/v1")
v1.GET("/users", listUsers) // GET /api/v1/users
v1.POST("/users", createUser) // POST /api/v1/users
v2 := api.Group("/v2")
v2.GET("/users", listUsersV2) // GET /api/v2/users
func (*RouteGroup) DELETE ¶
func (g *RouteGroup) DELETE(path string, handler HandlerFunc)
DELETE registers a DELETE route on the group.
Example:
api := router.Group("/api")
api.DELETE("/users/:id", func(c *Box) error {
return c.NoContent(204)
})
func (*RouteGroup) GET ¶
func (g *RouteGroup) GET(path string, handler HandlerFunc)
GET registers a GET route on the group.
Example:
api := router.Group("/api")
api.GET("/users", func(c *Box) error {
return c.JSON(200, users)
})
func (*RouteGroup) Group ¶
func (g *RouteGroup) Group(prefix string, middleware ...HandlerFunc) *RouteGroup
Group creates a new nested route group with the given prefix and optional middleware. The new group's prefix is the combination of the parent prefix and the new prefix. If no middleware is provided, the new group inherits the parent's middleware.
Example:
api := router.Group("/api")
api.Use(LoggerMiddleware())
v1 := api.Group("/v1") // Inherits logger
v1.Use(AuthMiddleware()) // Adds auth
v1.GET("/users", handler) // GET /api/v1/users (logger + auth)
v2 := api.Group("/v2", RateLimitMiddleware()) // Custom middleware
v2.GET("/users", handler) // GET /api/v2/users (ratelimit only)
func (*RouteGroup) HEAD ¶
func (g *RouteGroup) HEAD(path string, handler HandlerFunc)
HEAD registers a HEAD route on the group.
Example:
api := router.Group("/api")
api.HEAD("/users/:id", func(c *Box) error {
return c.NoContent(200)
})
func (*RouteGroup) Handle ¶
func (g *RouteGroup) Handle(method, path string, handler HandlerFunc)
Handle registers a route with the given HTTP method, path, and handler. This is the core method used by all HTTP method shortcuts (GET, POST, etc.).
The final route path is: group.prefix + path The final middleware chain is: router.middleware + group.middleware + handler
Example:
api := router.Group("/api")
api.Handle("GET", "/users", handler) // Registers GET /api/users
func (*RouteGroup) OPTIONS ¶
func (g *RouteGroup) OPTIONS(path string, handler HandlerFunc)
OPTIONS registers an OPTIONS route on the group.
Example:
api := router.Group("/api")
api.OPTIONS("/users", func(c *Box) error {
c.SetHeader("Allow", "GET, POST")
return c.NoContent(200)
})
func (*RouteGroup) PATCH ¶
func (g *RouteGroup) PATCH(path string, handler HandlerFunc)
PATCH registers a PATCH route on the group.
Example:
api := router.Group("/api")
api.PATCH("/users/:id", func(c *Box) error {
return c.JSON(200, patchedUser)
})
func (*RouteGroup) POST ¶
func (g *RouteGroup) POST(path string, handler HandlerFunc)
POST registers a POST route on the group.
Example:
api := router.Group("/api")
api.POST("/users", func(c *Box) error {
return c.JSON(201, newUser)
})
func (*RouteGroup) PUT ¶
func (g *RouteGroup) PUT(path string, handler HandlerFunc)
PUT registers a PUT route on the group.
Example:
api := router.Group("/api")
api.PUT("/users/:id", func(c *Box) error {
return c.JSON(200, updatedUser)
})
func (*RouteGroup) Use ¶
func (g *RouteGroup) Use(middleware ...HandlerFunc) *RouteGroup
Use registers middleware to the route group. Group middleware is executed after router middleware but before route handlers.
Middleware order: Router.Use() → Group.Use() → Handler
Example:
api := router.Group("/api")
api.Use(LoggerMiddleware())
api.Use(AuthMiddleware())
Can be chained:
api.Use(Logger()).Use(Auth())
type RouteInfo ¶
type RouteInfo struct {
// Method is the HTTP method (GET, POST, etc.).
Method string
// Path is the route path (e.g., "/users/:id").
Path string
// Summary is a short description of the operation.
Summary string
// Description is a detailed description of the operation.
Description string
// Tags are categories for documentation grouping.
Tags []string
// OperationID is a unique identifier for the operation.
OperationID string
// Deprecated: indicates if this route is deprecated.
Deprecated bool
// RequestType is the Go type for the request body (if any).
RequestType reflect.Type
// ResponseType is the Go type for the response body (if any).
ResponseType reflect.Type
// Parameters stores metadata about path/query/header parameters.
Parameters []RouteParameter
// Responses stores metadata about possible responses.
Responses map[int]RouteResponse
}
RouteInfo stores metadata about a registered route. This information is used for OpenAPI generation, documentation, and introspection.
type RouteOptions ¶
type RouteOptions struct {
// Summary is a short description of the operation.
Summary string
// Description is a detailed description of the operation.
Description string
// Tags are categories for documentation grouping.
Tags []string
// OperationID is a unique identifier for the operation.
OperationID string
// Deprecated: indicates if this route is deprecated.
Deprecated bool
// Parameters stores metadata about path/query/header parameters.
Parameters []RouteParameter
// Responses stores metadata about possible responses.
Responses map[int]RouteResponse
}
RouteOptions allows configuring route metadata when registering a route.
type RouteParameter ¶
type RouteParameter struct {
// Name of the parameter.
Name string
// In specifies the location: "path", "query", "header", "cookie".
In string
// Description of the parameter.
Description string
// Required indicates if the parameter is required.
Required bool
// Type is the Go type of the parameter.
Type reflect.Type
}
RouteParameter stores metadata about a route parameter.
type RouteResponse ¶
type RouteResponse struct {
// Description of the response.
Description string
// Type is the Go type of the response body.
Type reflect.Type
// ContentType is the media type (e.g., "application/json").
ContentType string
}
RouteResponse stores metadata about a response.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is the main HTTP router for FURSY. It provides fast URL routing with support for static paths, parameters (:id), and wildcards (*path).
Router implements http.Handler and can be used directly with http.ListenAndServe.
func New ¶
func New() *Router
New creates a new Router instance with default configuration.
The router is created with:
- Context pooling enabled for zero allocations
- Method Not Allowed handling enabled
- OPTIONS handling enabled
- Empty routing tables (trees are created on first route registration)
func (*Router) DELETE ¶
func (r *Router) DELETE(path string, handler HandlerFunc)
DELETE registers a handler for DELETE requests to the specified path.
Example:
router.DELETE("/users/:id", func(c *fursy.Box) error {
id := c.Param("id")
return c.NoContent(204)
})
func (*Router) GET ¶
func (r *Router) GET(path string, handler HandlerFunc)
GET registers a handler for GET requests to the specified path.
Example:
router.GET("/users", func(c *fursy.Box) error {
return c.JSON(200, users)
})
func (*Router) GenerateOpenAPI ¶
GenerateOpenAPI generates an OpenAPI 3.1 document from the router.
This method introspects all registered routes and generates a complete OpenAPI 3.1 specification including paths, schemas, and components.
If info is not provided via WithInfo(), the info parameter is used.
Example:
doc, err := router.GenerateOpenAPI(Info{
Title: "My API",
Version: "1.0.0",
})
func (*Router) Group ¶
func (r *Router) Group(prefix string, middleware ...HandlerFunc) *RouteGroup
Group creates a new route group with the given path prefix and optional middleware. Groups allow organizing routes hierarchically and applying middleware to specific route sets.
The group inherits router middleware but can have its own middleware stack. Middleware order: Router.Use() → Group.Use() → Handler
Example:
router := fursy.New()
router.Use(LoggerMiddleware()) // Global
api := router.Group("/api")
api.Use(AuthMiddleware()) // API-specific
v1 := api.Group("/v1")
v1.GET("/users", handler) // GET /api/v1/users (logger + auth)
admin := api.Group("/admin", AdminMiddleware()) // Custom middleware
admin.GET("/settings", handler) // GET /api/admin/settings (admin only)
func (*Router) HEAD ¶
func (r *Router) HEAD(path string, handler HandlerFunc)
HEAD registers a handler for HEAD requests to the specified path.
Example:
router.HEAD("/users/:id", func(c *fursy.Box) error {
return c.NoContent(200)
})
func (*Router) Handle ¶
func (r *Router) Handle(method, path string, handler HandlerFunc)
Handle registers a handler for the given HTTP method and path.
The method must be a valid HTTP method (GET, POST, etc.). The path must start with a '/' and can contain:
- Static segments: /users
- Named parameters: /users/:id
- Catch-all parameters: /files/*path
Panics if method or path is empty, or if handler is nil.
Example:
router.Handle("GET", "/users/:id", func(c *fursy.Box) error {
id := c.Param("id")
return c.String(200, "User ID: "+id)
})
func (*Router) HandleWithOptions ¶
func (r *Router) HandleWithOptions(method, path string, handler HandlerFunc, opts *RouteOptions)
HandleWithOptions registers a handler with route metadata for OpenAPI generation.
This method extends Handle() with support for route documentation.
Example:
router.HandleWithOptions("GET", "/users/:id", handler, &RouteOptions{
Summary: "Get user by ID",
Description: "Returns a single user",
Tags: []string{"users"},
})
func (*Router) ListenAndServeWithShutdown ¶
ListenAndServeWithShutdown starts the HTTP server with automatic graceful shutdown.
This is a convenience method that:
- Creates an http.Server with the given address
- Listens for SIGTERM and SIGINT signals (Kubernetes/Docker compatible)
- Starts the server in a goroutine
- Blocks until shutdown signal is received
- Calls Shutdown() with the specified timeout (default: 30s)
The timeout must be less than Kubernetes terminationGracePeriodSeconds (default 30s) to allow time for preStop hooks and connection draining.
Returns:
- nil if shutdown completed successfully
- http.ErrServerClosed is treated as successful shutdown (not returned)
- Error from server startup (e.g., address in use)
- Error from Shutdown if timeout exceeded
Example (simple):
router := fursy.New()
router.GET("/health", healthHandler)
router.OnShutdown(func() {
log.Println("Closing database...")
db.Close()
})
// Blocks until SIGTERM/SIGINT, then graceful shutdown with 30s timeout
if err := router.ListenAndServeWithShutdown(":8080"); err != nil {
log.Fatal(err)
}
Example (custom timeout):
// 10 second shutdown timeout
if err := router.ListenAndServeWithShutdown(":8080", 10*time.Second); err != nil {
log.Fatal(err)
}
Example (Kubernetes-ready):
router := fursy.New()
// Health check for readiness probe
router.GET("/health", func(c *fursy.Context) error {
return c.String(200, "OK")
})
// Cleanup on shutdown
router.OnShutdown(func() {
log.Println("Shutdown initiated...")
db.Close()
cache.Close()
log.Println("Cleanup complete")
})
// Kubernetes sends SIGTERM before killing pod
// terminationGracePeriodSeconds: 30s (default)
// Our shutdown timeout: 25s (leaves 5s buffer)
if err := router.ListenAndServeWithShutdown(":8080", 25*time.Second); err != nil {
log.Fatal(err)
}
func (*Router) OPTIONS ¶
func (r *Router) OPTIONS(path string, handler HandlerFunc)
OPTIONS registers a handler for OPTIONS requests to the specified path.
Example:
router.OPTIONS("/users", func(c *fursy.Box) error {
c.SetHeader("Allow", "GET, POST, PUT, DELETE")
return c.NoContent(200)
})
func (*Router) OnShutdown ¶
func (r *Router) OnShutdown(f func())
OnShutdown registers a function to be called during graceful shutdown.
Callbacks are executed in reverse order (last registered, first called) before the HTTP server stops accepting new connections.
Use this for cleanup tasks like:
- Closing database connections
- Flushing logs or metrics
- Saving in-memory data
- Releasing external resources
OnShutdown is safe for concurrent use.
Example:
router := fursy.New()
// Register cleanup callbacks
router.OnShutdown(func() {
log.Println("Closing database connections...")
db.Close()
})
router.OnShutdown(func() {
log.Println("Flushing metrics...")
metrics.Flush()
})
// Start server with graceful shutdown
if err := router.ListenAndServeWithShutdown(":8080"); err != nil {
log.Fatal(err)
}
func (*Router) PATCH ¶
func (r *Router) PATCH(path string, handler HandlerFunc)
PATCH registers a handler for PATCH requests to the specified path.
Example:
router.PATCH("/users/:id", func(c *fursy.Box) error {
id := c.Param("id")
return c.JSON(200, updatedUser)
})
func (*Router) POST ¶
func (r *Router) POST(path string, handler HandlerFunc)
POST registers a handler for POST requests to the specified path.
Example:
router.POST("/users", func(c *fursy.Box) error {
return c.JSON(201, newUser)
})
func (*Router) PUT ¶
func (r *Router) PUT(path string, handler HandlerFunc)
PUT registers a handler for PUT requests to the specified path.
Example:
router.PUT("/users/:id", func(c *fursy.Box) error {
id := c.Param("id")
return c.NoContent(204)
})
func (*Router) ServeHTTP ¶
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP implements http.Handler interface, making Router compatible with the standard library's http.Server.
It performs the following steps:
- Gets a Context from the pool (zero allocation)
- Looks up the appropriate routing tree for the HTTP method
- Searches for a matching route in the radix tree
- Extracts URL parameters if the route contains wildcards
- Initializes the Context with request/response/params
- Builds middleware chain (global middleware + route handler)
- Executes the middleware chain via c.Next()
- Resets and returns Context to the pool
Returns 404 Not Found if no route matches the path. Returns 405 Method Not Allowed if the path exists but for a different method (when handleMethodNotAllowed is enabled).
func (*Router) ServeOpenAPI ¶
ServeOpenAPI registers a route that serves the OpenAPI 3.1 specification as JSON.
This is a convenience method that automatically generates and serves the OpenAPI document at the specified path. The document is generated on each request, so it always reflects the current state of registered routes.
For production use, consider caching the generated document or serving a pre-generated specification file.
Example:
router := fursy.New()
router.WithInfo(fursy.Info{
Title: "My API",
Version: "1.0.0",
})
// Register your routes
router.GET("/users", handler)
router.POST("/users", handler)
// Serve OpenAPI specification
router.ServeOpenAPI("/openapi.json")
// Now GET /openapi.json returns the OpenAPI 3.1 document
func (*Router) SetServer ¶
SetServer sets the http.Server for graceful shutdown.
This is typically called by ListenAndServeWithShutdown, but can be used manually if you create the server yourself.
Example:
router := fursy.New()
srv := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
router.SetServer(srv)
router.OnShutdown(func() {
log.Println("Server shutting down...")
})
// Now router.Shutdown() will shutdown srv
func (*Router) SetValidator ¶
SetValidator sets the validator for automatic request validation.
When a validator is set, Box.Bind() will automatically validate request bodies after binding. If validation fails, Bind() returns a ValidationErrors error.
Validator is optional. If not set, binding works without validation.
Example:
// Using validator/v10 (requires plugin)
import "github.com/coregx/fursy/plugins/validator"
router := fursy.New()
router.SetValidator(validator.New())
// Now all POST/PUT/PATCH requests will be validated
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=18,lte=120"`
}
POST[CreateUserRequest, UserResponse](router, "/users", func(c *Box[CreateUserRequest, UserResponse]) error {
// c.ReqBody is already validated here!
// ...
})
func (*Router) Shutdown ¶
Shutdown gracefully shuts down the HTTP server and executes registered callbacks.
Shutdown works in two phases:
- Calls all registered OnShutdown callbacks in reverse order
- Calls http.Server.Shutdown() to gracefully stop the server
The server shutdown process:
- Immediately closes all listeners (stops accepting new connections)
- Waits for active requests to complete (respects context timeout)
- Returns context error if timeout is exceeded
Shutdown does NOT forcefully close active connections after timeout. If you need to force close, cancel the context.
Safe to call multiple times (subsequent calls are no-ops).
Example:
router := fursy.New()
router.OnShutdown(func() {
log.Println("Cleanup...")
})
// Start server in goroutine
srv := &http.Server{Addr: ":8080", Handler: router}
router.SetServer(srv)
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
// Handle shutdown signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<-sigChan
// Graceful shutdown with 30s timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := router.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
func (*Router) Use ¶
func (r *Router) Use(middleware ...HandlerFunc) *Router
Use registers global middleware that executes for all routes. Middleware is executed in the order it is registered.
Middleware can:
- Modify the request/response
- Call c.Next() to continue the chain
- Call c.Abort() to stop the chain
- Return an error to propagate to error handler
Example:
router.Use(Logger())
router.Use(Recovery())
router.Use(CORS())
func Logger() HandlerFunc {
return func(c *Context) error {
start := time.Now()
err := c.Next()
log.Printf("%s - %v", c.Request.URL.Path, time.Since(start))
return err
}
}
func (*Router) WithInfo ¶
WithInfo sets the API metadata for OpenAPI generation.
This configures the info section of the generated OpenAPI document.
Example:
router.WithInfo(Info{
Title: "My API",
Version: "1.0.0",
Description: "A sample API built with FURSY",
})
func (*Router) WithServer ¶
WithServer adds a server to the OpenAPI document.
Servers define the base URLs where the API is deployed.
Example:
router.WithServer(Server{
URL: "https://api.example.com",
Description: "Production server",
})
type Schema ¶
type Schema struct {
// Type specifies the data type.
Type string `json:"type,omitempty"`
// Format provides additional type information.
Format string `json:"format,omitempty"`
// Title of the schema.
Title string `json:"title,omitempty"`
// Description of the schema.
Description string `json:"description,omitempty"`
// Properties for object types (property name -> schema).
Properties map[string]*Schema `json:"properties,omitempty"`
// Required lists required properties for object types.
Required []string `json:"required,omitempty"`
// Items schema for array types.
Items *Schema `json:"items,omitempty"`
// Enum restricts values to a specific set.
Enum []any `json:"enum,omitempty"`
// Default value.
Default any `json:"default,omitempty"`
// Example value.
Example any `json:"example,omitempty"`
// Nullable indicates if the value can be null.
Nullable bool `json:"nullable,omitempty"`
// ReadOnly indicates the property is read-only.
ReadOnly bool `json:"readOnly,omitempty"`
// WriteOnly indicates the property is write-only.
WriteOnly bool `json:"writeOnly,omitempty"`
// Ref is a reference to another schema.
Ref string `json:"$ref,omitempty"`
// AdditionalProperties for object types.
AdditionalProperties any `json:"additionalProperties,omitempty"`
// OneOf specifies that the value must match exactly one schema.
OneOf []*Schema `json:"oneOf,omitempty"`
// AnyOf specifies that the value must match at least one schema.
AnyOf []*Schema `json:"anyOf,omitempty"`
// AllOf specifies that the value must match all schemas.
AllOf []*Schema `json:"allOf,omitempty"`
}
Schema represents a data type schema. This is compatible with JSON Schema Draft 2020-12.
type SecurityRequirement ¶
SecurityRequirement represents a security requirement.
type SecurityScheme ¶
type SecurityScheme struct {
Type string `json:"type"`
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Scheme string `json:"scheme,omitempty"`
BearerFormat string `json:"bearerFormat,omitempty"`
Flows *OAuthFlows `json:"flows,omitempty"`
OpenIDConnectURL string `json:"openIdConnectUrl,omitempty"`
}
SecurityScheme defines a security scheme.
type Server ¶
type Server struct {
// URL to the target host.
URL string `json:"url"`
// Description is an optional string describing the host.
Description string `json:"description,omitempty"`
// Variables for server URL template substitution.
Variables map[string]ServerVariable `json:"variables,omitempty"`
}
Server represents a server.
type ServerVariable ¶
type ServerVariable struct {
Enum []string `json:"enum,omitempty"`
Default string `json:"default"`
Description string `json:"description,omitempty"`
}
ServerVariable represents a server variable for template substitution.
type ValidationError ¶
type ValidationError struct {
// Field is the name of the field that failed validation.
// For nested structs, uses dot notation (e.g., "Address.City").
Field string `json:"field"`
// Tag is the validation rule that failed (e.g., "required", "email", "min").
Tag string `json:"tag"`
// Value is the actual value that failed validation.
// Omitted from JSON if nil to avoid exposing sensitive data.
Value any `json:"value,omitempty"`
// Message is a human-readable error message.
Message string `json:"message"`
}
ValidationError represents a single field validation error.
It provides structured information about what field failed validation, which rule was violated, and a human-readable message.
func (*ValidationError) Error ¶
func (ve *ValidationError) Error() string
Error implements the error interface.
type ValidationErrors ¶
type ValidationErrors []ValidationError
ValidationErrors is a collection of validation errors.
It implements the error interface and provides methods for error formatting.
func (*ValidationErrors) Add ¶
func (ve *ValidationErrors) Add(field, tag, message string)
Add adds a validation error to the collection.
func (*ValidationErrors) AddError ¶
func (ve *ValidationErrors) AddError(err ValidationError)
AddError adds a validation error struct to the collection.
func (ValidationErrors) Error ¶
func (ve ValidationErrors) Error() string
Error implements the error interface. Returns a concatenated string of all validation errors.
func (ValidationErrors) Fields ¶
func (ve ValidationErrors) Fields() map[string]string
Fields returns a map of field names to their error messages. Useful for API responses with per-field error details.
Example:
{
"email": "must be a valid email address",
"age": "must be at least 18"
}
func (ValidationErrors) IsEmpty ¶
func (ve ValidationErrors) IsEmpty() bool
IsEmpty returns true if there are no validation errors.
type Validator ¶
type Validator interface {
// Validate validates the given struct and returns validation errors.
// Returns nil if validation passes.
// Returns ValidationErrors for field-level errors.
Validate(any) error
}
Validator is the interface for request validation.
Implementations can use any validation library (validator/v10, ozzo-validation, custom, etc.) or implement custom validation logic.
Example implementations:
- plugins/validator: go-playground/validator/v10 integration
- Custom validator with business rules
type Version ¶
Version represents an API version.
FURSY supports semantic versioning (MAJOR.MINOR.PATCH) but commonly uses only major versions for API versioning (v1, v2, etc.).
Example:
v1 := fursy.Version{Major: 1}
v2 := fursy.Version{Major: 2, Minor: 1}
func ExtractVersionFromPath ¶
ExtractVersionFromPath extracts version from URL path.
Supports patterns:
- /v1/users -> v1
- /api/v2/posts -> v2
- /api/v2.1/posts -> v2.1
Returns zero Version and false if no version found.
func ParseVersion ¶
ParseVersion parses a version string into a Version struct.
Supported formats:
- "v1" -> Version{Major: 1}
- "v2.1" -> Version{Major: 2, Minor: 1}
- "v3.2.1" -> Version{Major: 3, Minor: 2, Patch: 1}
- "1" -> Version{Major: 1}
- "2.1.0" -> Version{Major: 2, Minor: 1}
Returns zero Version and false if parsing fails.
func (Version) GreaterThan ¶
GreaterThan checks if this version is greater than another.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
binding
Package binding provides request body binding functionality.
|
Package binding provides request body binding functionality. |
|
negotiate
Package negotiate provides RFC 9110 compliant content negotiation.
|
Package negotiate provides RFC 9110 compliant content negotiation. |
|
radix
Package radix implements a radix tree (compressed trie) for HTTP routing.
|
Package radix implements a radix tree (compressed trie) for HTTP routing. |
|
Package middleware provides HTTP Basic Authentication middleware.
|
Package middleware provides HTTP Basic Authentication middleware. |
|
plugins
|
|
|
database
module
|
|
|
validator
module
|