- Go 100%
| middleware | ||
| .gitignore | ||
| context.go | ||
| error_handler.go | ||
| go.mod | ||
| go.sum | ||
| group.go | ||
| handler.go | ||
| kite.go | ||
| LICENSE | ||
| map.go | ||
| method_not_allowed_handler.go | ||
| middleware.go | ||
| not_found_handler.go | ||
| README.md | ||
| response_writer.go | ||
🪁 Kite — A Fast, Lightweight Go HTTP Framework
Kite is a high-performance, minimalist HTTP framework for Go built on top of httprouter. It provides an elegant, Express-like API with first-class support for middleware, route grouping, WebSockets, static files, SPAs, and graceful shutdown — all with zero unnecessary allocations.
Table of Contents
- Installation
- Quick Start
- Core Concepts
- Routing
- Request Handling
- Response Handling
- Middleware (Built-in)
- Static Files & SPA
- WebSockets
- Server Configuration
- Custom Handlers
- Complete Example
- API Reference
- License
Installation
go get git.trcreatives.at/packages/go-kite
Kite depends on three well-known, lightweight libraries:
| Dependency | Purpose |
|---|---|
github.com/julienschmidt/httprouter |
High-performance radix-tree router |
github.com/gorilla/websocket |
WebSocket support |
github.com/gorilla/schema |
Form/multipart decoding |
Quick Start
package main
import (
"net/http"
"git.trcreatives.at/packages/go-kite"
"git.trcreatives.at/packages/go-kite/middleware"
)
func main() {
k := kite.New()
// Global middleware
k.Use(middleware.Logger())
k.Use(middleware.Recovery())
k.Use(middleware.RequestID())
// Routes
k.GET("/", func(ctx *kite.Context) error {
return ctx.WriteString(http.StatusOK, "Hello, World!")
})
k.GET("/hello/:name", func(ctx *kite.Context) error {
name := ctx.PathParam("name")
return ctx.WriteJSON(http.StatusOK, map[string]string{
"message": "Hello, " + name + "!",
})
})
k.Start(":8080")
}
Visit http://localhost:8080/ → Hello, World!
Visit http://localhost:8080/hello/Kite → {"message":"Hello, Kite!"}
Core Concepts
Handler
Every route handler is a function with the signature:
type Handler func(ctx *Context) error
Returning a non-nil error triggers the framework's error handler, which by default writes a 500 response with the error message.
k.GET("/example", func(ctx *kite.Context) error {
// Do work...
return nil // or return an error
})
Middleware
Middleware wraps a Handler and returns a new Handler:
type Middleware func(h Handler) Handler
A middleware can:
- Execute code before the handler
- Execute code after the handler
- Short-circuit the request (not call the next handler)
- Modify the context
// Example: custom timing middleware
func TimingMiddleware() kite.Middleware {
return func(h kite.Handler) kite.Handler {
return func(ctx *kite.Context) error {
start := time.Now()
err := h(ctx)
elapsed := time.Since(start)
ctx.SetHeader("X-Response-Time", elapsed.String())
return err
}
}
}
Context
The Context is the heart of every request. It provides:
- Access to the underlying
*http.Requestandhttp.ResponseWriter - Path parameter and query parameter reading
- Request body binding (JSON, XML, forms, multipart)
- Response writing helpers (JSON, XML, string, stream, redirect)
- Cookie and header manipulation
- Per-request key/value store
Error Handling
When a handler returns an error, the framework calls the configured ErrorHandler:
type ErrorHandler func(ctx *Context, err error) error
The default writes 500 internal server error with err.Error() as the body. You can replace it:
k.SetErrorHandler(func(ctx *kite.Context, err error) error {
return ctx.WriteJSON(http.StatusInternalServerError, map[string]string{
"error": err.Error(),
})
})
Routing
Kite uses httprouter under the hood, giving you fast, radix-tree-based routing with zero allocations during path matching.
HTTP Method Helpers
All standard HTTP methods are available on both *Kite and *Group:
| Method | Helper |
|---|---|
GET |
.GET(path, handler, middleware...) |
POST |
.POST(path, handler, middleware...) |
PUT |
.PUT(path, handler, middleware...) |
PATCH |
.PATCH(path, handler, middleware...) |
DELETE |
.DELETE(path, handler, middleware...) |
HEAD |
.HEAD(path, handler, middleware...) |
OPTIONS |
.OPTIONS(path, handler, middleware...) |
CONNECT |
.CONNECT(path, handler, middleware...) |
TRACE |
.TRACE(path, handler, middleware...) |
Each accepts optional route-level middleware that runs after global/group middleware but before the handler.
Path Parameters
Kite supports :param and *wildcard syntax from httprouter:
// Named parameter
k.GET("/users/:id", func(ctx *kite.Context) error {
id := ctx.PathParam("id") // "42" for /users/42
return ctx.WriteString(200, "User: "+id)
})
// Wildcard (catch-all)
k.GET("/files/*filepath", func(ctx *kite.Context) error {
path := ctx.PathParam("filepath") // "/css/style.css" for /files/css/style.css
return ctx.WriteString(200, "File: "+path)
})
Route Groups
Groups allow you to share a common path prefix and middleware:
api := k.Group("/api")
api.Use(AuthMiddleware())
// These are all under /api and have AuthMiddleware applied
api.GET("/users", listUsers)
api.POST("/users", createUser)
api.GET("/users/:id", getUser)
// Nested groups
admin := api.Group("/admin")
admin.Use(AdminOnlyMiddleware())
admin.DELETE("/users/:id", deleteUser)
Group-level middleware is applied in order: parent group middleware first, then child group middleware, then route-level middleware.
Global Middleware
Applied to every route via k.Use():
k.Use(middleware.Logger())
k.Use(middleware.Recovery())
k.Use(middleware.CORS())
Global middleware wraps the entire request pipeline, running before any group or route middleware.
Request Handling
Reading Query Parameters
k.GET("/search", func(ctx *kite.Context) error {
q := ctx.QueryParam("q") // ?q=hello → "hello"
page := ctx.QueryParam("page") // ?page=2 → "2" (empty string if missing)
return ctx.WriteString(200, "Search: "+q+", Page: "+page)
})
Reading Path Parameters
k.GET("/posts/:slug", func(ctx *kite.Context) error {
slug := ctx.PathParam("slug")
return ctx.WriteString(200, "Post: "+slug)
})
Reading Headers
k.GET("/headers", func(ctx *kite.Context) error {
authorization := ctx.Header("Authorization")
contentType := ctx.Header("Content-Type")
return ctx.WriteJSON(200, map[string]string{
"auth": authorization,
"ct": contentType,
})
})
Reading Cookies
k.GET("/profile", func(ctx *kite.Context) error {
cookie, err := ctx.Cookie("session_id")
if err != nil {
return ctx.WriteString(401, "Not authenticated")
}
return ctx.WriteString(200, "Session: "+cookie.Value)
})
Reading the Request Body
k.POST("/raw", func(ctx *kite.Context) error {
body, err := ctx.Body()
if err != nil {
return err
}
return ctx.WriteString(200, "Received: "+string(body))
})
Form & Multipart Data
URL-encoded forms:
k.POST("/login", func(ctx *kite.Context) error {
username := ctx.FormValue("username")
password := ctx.FormValue("password")
return ctx.WriteJSON(200, map[string]string{"user": username})
})
Multipart file uploads:
k.POST("/upload", func(ctx *kite.Context) error {
// Parse multipart form with 32MB limit
form, err := ctx.MultipartForm(32 << 20)
if err != nil {
return err
}
// Access files
files := form.File["files"]
for _, fh := range files {
// Process fh...
}
return ctx.WriteNoContent()
})
// Or get a single file directly
k.POST("/avatar", func(ctx *kite.Context) error {
file, header, err := ctx.MultipartFormFile("avatar")
if err != nil {
return err
}
defer file.Close()
// Process file...
return ctx.WriteString(200, "Uploaded: "+header.Filename)
})
Binding
The Bind method automatically detects the Content-Type and decodes the request body into a Go struct. Supported content types:
| Content-Type | Decoder |
|---|---|
application/json |
json.Decoder |
application/xml |
xml.Decoder |
application/x-www-form-urlencoded |
gorilla/schema |
multipart/form-data |
gorilla/schema |
type CreateUserInput struct {
Name string `json:"name" schema:"name"`
Email string `json:"email" schema:"email"`
Age int `json:"age" schema:"age"`
}
k.POST("/users", func(ctx *kite.Context) error {
var input CreateUserInput
if err := ctx.Bind(&input); err != nil {
return err
}
// input is populated
return ctx.WriteJSON(201, input)
})
Individual binding methods are also available:
ctx.BindJSON(&v)— JSON onlyctx.BindXML(&v)— XML onlyctx.BindForm(&v)— Form / multipartctx.BindFormWithMaxMemory(&v, maxMemory)— Form with custom memory limitctx.BindWithMaxMemory(&v, maxMemory)— Auto-detect with custom memory limit
Client IP
Respects X-Forwarded-For and X-Real-IP headers for proxy-aware IP resolution:
k.GET("/ip", func(ctx *kite.Context) error {
return ctx.WriteString(200, ctx.IP())
})
Fallback order: X-Forwarded-For → X-Real-IP → RemoteAddr.
Response Handling
Writing Strings
ctx.WriteString(http.StatusOK, "Hello, World!")
Sets Content-Type: text/plain; charset=utf-8.
Writing JSON
ctx.WriteJSON(http.StatusOK, map[string]any{
"status": "ok",
"data": someStruct,
})
Sets Content-Type: application/json; charset=utf-8.
Writing XML
ctx.WriteXML(http.StatusOK, someStruct)
Sets Content-Type: application/xml; charset=utf-8.
Writing Raw Bytes
ctx.WriteBytes(http.StatusOK, "image/png", imageBytes)
No Content (204)
ctx.WriteNoContent()
Writes a 204 No Content status with no body.
Streaming Responses
k.GET("/download", func(ctx *kite.Context) error {
file, _ := os.Open("large-file.zip")
defer file.Close()
return ctx.Stream(http.StatusOK, "application/zip", file)
})
Copies from an io.Reader directly to the response writer.
Redirects
ctx.Redirect(http.StatusFound, "/new-location")
Setting Headers & Cookies
// Headers
ctx.SetHeader("X-Custom", "value") // Set (overwrites)
ctx.AddHeader("X-Custom", "another") // Add (appends)
// Cookies
ctx.SetCookie(&http.Cookie{
Name: "session",
Value: "abc123",
Path: "/",
})
ctx.ClearCookie("session") // Removes a cookie by setting MaxAge=-1
Middleware (Built-in)
Kite ships with five production-ready middleware in the middleware package.
CORS
import "git.trcreatives.at/packages/go-kite/middleware"
// Simple: allow all origins
k.Use(middleware.CORS())
// With specific origins
k.Use(middleware.CORS("https://example.com", "https://app.example.com"))
// Full configuration
k.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowedOrigins: []string{"https://example.com"},
AllowedMethods: []string{"GET", "POST", "PUT"},
AllowedHeaders: []string{"Content-Type", "Authorization", "X-API-Key"},
ExposedHeaders: []string{"X-Request-ID", "X-Total-Count"},
AllowCredentials: true,
MaxAge: 3600, // seconds
}))
| Config Field | Default | Description |
|---|---|---|
AllowedOrigins |
["*"] |
Allowed origin patterns |
AllowedMethods |
All 9 methods | Allowed HTTP methods |
AllowedHeaders |
["Content-Type", "Authorization"] |
Allowed request headers |
ExposedHeaders |
[] |
Headers exposed to the browser |
AllowCredentials |
false |
Allow cookies/auth headers |
MaxAge |
0 |
Preflight cache duration (seconds) |
Special behavior: When AllowedOrigins is ["*"] and AllowCredentials is true, the origin is echoed back instead of * (as required by the CORS spec). Preflight OPTIONS requests are handled automatically — the middleware returns 204 No Content.
Logger
// Default: logs to stdout
k.Use(middleware.Logger())
// Custom configuration
k.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
Output: logFile, // io.Writer
Format: func(method, path string, statusCode int, latency time.Duration) string {
return fmt.Sprintf("%s %s → %d (%s)", method, path, statusCode, latency)
},
Skip: func(ctx *kite.Context) bool {
return ctx.Path() == "/health" // don't log health checks
},
}))
Default format: [Kite] GET /users/42 -> 200 in 1.234ms
Recovery (Panic Handling)
// Default: logs panic and returns 500
k.Use(middleware.Recovery())
// Custom panic handler
k.Use(middleware.RecoveryWithConfig(middleware.RecoveryConfig{
OnPanic: func(ctx *kite.Context, recovered any, stack []byte) error {
log.Printf("PANIC: %v\n%s", recovered, stack)
return ctx.WriteJSON(500, map[string]string{
"error": "Internal server error",
})
},
}))
Must be registered before other middleware to catch panics from the entire stack.
Request ID
// Default: uses X-Request-ID header, generates UUID-like ID if missing
k.Use(middleware.RequestID())
// Custom configuration
k.Use(middleware.RequestIDWithConfig(middleware.RequestIDConfig{
Header: "X-Correlation-ID",
Generator: func() string {
return uuid.New().String()
},
}))
The ID is both set as a response header and stored in the context, accessible via:
id := ctx.Value("X-Request-ID") // or whatever header name you configured
It reads an incoming ID from the request header if present, otherwise generates one.
Max Body Size
// Default: 4 MB
k.Use(middleware.MaxBodySize(4 << 20))
// Custom limit
k.Use(middleware.MaxBodySize(1 << 20)) // 1 MB
k.Use(middleware.MaxBodySizeWithConfig(middleware.MaxBodySizeConfig{
MaxBytes: 10 << 20, // 10 MB
}))
Wraps the request body with http.MaxBytesReader. If a client exceeds the limit, reading the body will return an error.
Static Files & SPA
Serving Static Files
// Serve ./public at /static/
k.Static("/static", "./public")
// GET /static/style.css → ./public/style.css
// GET /static/js/app.js → ./public/js/app.js
// With custom http.FileSystem
k.StaticFS("/assets", http.Dir("./build"))
// With middleware (e.g., caching headers)
k.Static("/static", "./public", cacheMiddleware())
Single Page Application (SPA) Mode
Redirects all unmatched routes to index.html:
k.SPA("./dist")
// OR
k.SPAFS(http.Dir("./dist"))
Under the hood, this replaces the NotFoundHandler. If a file exists at the requested path, it is served; otherwise, the request is rewritten to / and served by the file server (typically hitting index.html).
⚠️ Calling
SPA/SPAFSafterSetNotFoundHandlerwill override it, and vice versa. Kite logs a warning when this happens.
Static Files on Groups
admin := k.Group("/admin")
admin.Static("/assets", "./admin-assets")
// Serves ./admin-assets at /admin/assets/
WebSockets
import "github.com/gorilla/websocket"
// Default upgrader (allows all origins)
k.WebSocket("/ws", func(conn *websocket.Conn) error {
for {
msgType, msg, err := conn.ReadMessage()
if err != nil {
return err // closes connection
}
conn.WriteMessage(msgType, msg) // echo
}
})
// Custom upgrader
upgrader := websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
return r.Header.Get("Origin") == "https://trusted.example.com"
},
}
k.WebSocketWithUpgrader("/secure-ws", upgrader, handleWebSocket)
// With middleware (runs before upgrade)
k.WebSocket("/ws", handleWebSocket, authMiddleware())
WebSocket routes can also be defined on groups:
api := k.Group("/api")
api.WebSocket("/realtime", handleRealtime, authMiddleware())
Server Configuration
Timeouts
k := kite.New()
k.SetServerReadTimeout(30 * time.Second) // default: 10s
k.SetServerWriteTimeout(30 * time.Second) // default: 30s
k.SetServerIdleTimeout(60 * time.Second) // default: 60s
k.SetServerShutdownTimeout(30 * time.Second) // default: 30s
| Method | Controls |
|---|---|
SetServerReadTimeout |
Max time to read the entire request (body included) |
SetServerWriteTimeout |
Max time to write the response |
SetServerIdleTimeout |
Max idle time for keep-alive connections |
SetServerShutdownTimeout |
Max time to wait for active connections during shutdown |
Starting the Server
// HTTP
if err := k.Start(":8080"); err != nil {
log.Fatal(err)
}
Start blocks until the server is shut down. It automatically handles SIGINT and SIGTERM for graceful shutdown.
TLS / HTTPS
if err := k.StartTLS(":443", "/path/to/cert.pem", "/path/to/key.pem"); err != nil {
log.Fatal(err)
}
Graceful Shutdown
Automatic on SIGINT/SIGTERM. For manual shutdown:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := k.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
Custom Handlers
Not Found (404)
k.SetNotFoundHandler(func(ctx *kite.Context) error {
return ctx.WriteJSON(http.StatusNotFound, map[string]string{
"error": "The requested resource was not found",
})
})
Method Not Allowed (405)
k.SetMethodNotAllowedHandler(func(ctx *kite.Context) error {
return ctx.WriteJSON(http.StatusMethodNotAllowed, map[string]string{
"error": "This method is not allowed for this endpoint",
})
})
Error Handler
k.SetErrorHandler(func(ctx *kite.Context, err error) error {
// Log the error
log.Printf("Error: %v", err)
// Return structured JSON
return ctx.WriteJSON(http.StatusInternalServerError, map[string]string{
"error": "Internal server error",
"request": ctx.Header("X-Request-ID"),
})
})
Complete Example
package main
import (
"log"
"net/http"
"time"
"git.trcreatives.at/packages/go-kite"
"git.trcreatives.at/packages/go-kite/middleware"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
k := kite.New()
// ── Server configuration ──
k.SetServerReadTimeout(15 * time.Second)
k.SetServerWriteTimeout(15 * time.Second)
k.SetServerShutdownTimeout(10 * time.Second)
// ── Global middleware ──
k.Use(middleware.RequestID())
k.Use(middleware.Logger())
k.Use(middleware.Recovery())
k.Use(middleware.CORS("http://localhost:3000"))
k.Use(middleware.MaxBodySize(5 << 20)) // 5 MB
// ── Custom error handler ──
k.SetErrorHandler(func(ctx *kite.Context, err error) error {
return ctx.WriteJSON(http.StatusInternalServerError, map[string]string{
"error": err.Error(),
"request_id": ctx.Value("X-Request-ID").(string),
})
})
// ── Custom 404 ──
k.SetNotFoundHandler(func(ctx *kite.Context) error {
return ctx.WriteJSON(http.StatusNotFound, map[string]string{
"error": "Not found",
})
})
// ── Public routes ──
k.GET("/health", func(ctx *kite.Context) error {
return ctx.WriteJSON(200, map[string]string{"status": "ok"})
})
// ── API group ──
api := k.Group("/api")
// Auth middleware for API
api.Use(func(h kite.Handler) kite.Handler {
return func(ctx *kite.Context) error {
if ctx.Header("Authorization") == "" {
return ctx.WriteString(401, "Unauthorized")
}
return h(ctx)
}
})
api.GET("/users", func(ctx *kite.Context) error {
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
return ctx.WriteJSON(200, users)
})
api.POST("/users", func(ctx *kite.Context) error {
var user User
if err := ctx.BindJSON(&user); err != nil {
return err
}
user.ID = 3
return ctx.WriteJSON(201, user)
})
api.GET("/users/:id", func(ctx *kite.Context) error {
id := ctx.PathParam("id")
return ctx.WriteJSON(200, User{ID: 1, Name: "User " + id})
})
// ── WebSocket ──
k.WebSocket("/ws", func(conn *websocket.Conn) error {
for {
mt, msg, err := conn.ReadMessage()
if err != nil {
return err
}
conn.WriteMessage(mt, msg)
}
})
// ── Static files ──
k.Static("/static", "./public")
// ── Start server ──
log.Fatal(k.Start(":8080"))
}
API Reference
Kite Struct
| Method | Description |
|---|---|
New() *Kite |
Create a new Kite instance with sensible defaults |
Start(addr string) error |
Start HTTP server with graceful shutdown |
StartTLS(addr, certFile, keyFile string) error |
Start HTTPS server with graceful shutdown |
Shutdown(ctx context.Context) error |
Manually shut down the server |
ServeHTTP(w http.ResponseWriter, r *http.Request) |
Implements http.Handler for use with httptest |
Use(mws ...Middleware) |
Register global middleware |
GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS/CONNECT/TRACE(path, handler, mws...) |
Register a route |
Group(prefix string, mws ...Middleware) *Group |
Create a route group |
Static(prefix, root string, mws ...Middleware) |
Serve static files from a directory |
StaticFS(prefix string, fs http.FileSystem, mws ...Middleware) |
Serve static files from an http.FileSystem |
SPA(root string) |
SPA mode from a directory |
SPAFS(fs http.FileSystem) |
SPA mode from an http.FileSystem |
WebSocket(path string, onConnect func(*websocket.Conn) error, mws ...Middleware) |
WebSocket endpoint with default upgrader |
WebSocketWithUpgrader(path string, upgrader websocket.Upgrader, onConnect func(*websocket.Conn) error, mws ...Middleware) |
WebSocket endpoint with custom upgrader |
SetErrorHandler(eh ErrorHandler) |
Set the error handler |
SetNotFoundHandler(nfh NotFoundHandler) |
Set the 404 handler |
SetMethodNotAllowedHandler(mnah MethodNotAllowedHandler) |
Set the 405 handler |
SetServerReadTimeout(d time.Duration) |
Set http.Server.ReadTimeout |
SetServerWriteTimeout(d time.Duration) |
Set http.Server.WriteTimeout |
SetServerIdleTimeout(d time.Duration) |
Set http.Server.IdleTimeout |
SetServerShutdownTimeout(d time.Duration) |
Set max wait time during graceful shutdown |
Context Struct
Request Methods
| Method | Returns | Description |
|---|---|---|
Request() *http.Request |
*http.Request |
Raw HTTP request |
Response() http.ResponseWriter |
http.ResponseWriter |
Raw response writer |
Context() context.Context |
context.Context |
Request context |
Method() string |
string |
HTTP method |
Path() string |
string |
Request URL path |
StatusCode() int |
int |
Current response status code |
PathParam(key string) string |
string |
URL path parameter |
QueryParam(key string) string |
string |
URL query parameter |
Header(key string) string |
string |
Request header value |
Cookie(key string) (*http.Cookie, error) |
*http.Cookie, error |
Named cookie |
Body() ([]byte, error) |
[]byte, error |
Full request body |
Form() (url.Values, error) |
url.Values, error |
Parsed form values |
FormValue(key string) string |
string |
Single form value |
MultipartForm(maxMemory int64) (*multipart.Form, error) |
*multipart.Form, error |
Parsed multipart form |
MultipartFormFile(key string) (multipart.File, *multipart.FileHeader, error) |
File, header, error | Single uploaded file |
IP() string |
string |
Client IP (proxy-aware) |
IsMethod(method string) bool |
bool |
Check HTTP method |
Binding Methods
| Method | Description |
|---|---|
Bind(v any) error |
Auto-detect Content-Type and decode (32MB form limit) |
BindWithMaxMemory(v any, maxMemory int64) error |
Auto-detect with custom form memory limit |
BindJSON(v any) error |
Decode JSON body |
BindXML(v any) error |
Decode XML body |
BindForm(v any) error |
Decode form/multipart (32MB limit) |
BindFormWithMaxMemory(v any, maxMemory int64) error |
Decode form with custom memory limit |
Response Methods
| Method | Description |
|---|---|
WriteString(statusCode int, v string) error |
Write plain text response |
WriteJSON(statusCode int, v any) error |
Write JSON response |
WriteXML(statusCode int, v any) error |
Write XML response |
WriteBytes(statusCode int, contentType string, v []byte) error |
Write raw bytes |
WriteNoContent() error |
Write 204 No Content |
Stream(statusCode int, contentType string, r io.Reader) error |
Stream from reader |
Redirect(statusCode int, url string) error |
HTTP redirect |
SetHeader(key, value string) |
Set response header (overwrites) |
AddHeader(key, value string) |
Add response header (appends) |
SetCookie(cookie *http.Cookie) |
Set a cookie |
ClearCookie(key string) |
Remove a cookie |
SetValue(key string, v any) |
Store value in request context |
Value(key string) any |
Retrieve value from request context |
Group Struct
| Method | Description |
|---|---|
Use(mws ...Middleware) |
Add middleware to group |
Group(prefix string, mws ...Middleware) *Group |
Create nested group |
GET/POST/PUT/... (all 9 methods) |
Register route under group prefix |
Static(prefix, root string, mws ...Middleware) |
Serve static files under group prefix |
StaticFS(prefix string, fs http.FileSystem, mws ...Middleware) |
Serve static files from http.FileSystem |
WebSocket(path string, onConnect func(*websocket.Conn) error, mws ...Middleware) |
WebSocket under group prefix |
WebSocketWithUpgrader(...) |
WebSocket with custom upgrader under group prefix |
Types
// Handler is a function that processes a request and returns an error.
type Handler func(ctx *Context) error
// Middleware wraps a Handler with cross-cutting logic.
type Middleware func(h Handler) Handler
// ErrorHandler handles errors returned by handlers.
type ErrorHandler func(ctx *Context, err error) error
// NotFoundHandler handles 404 responses.
type NotFoundHandler Handler
// MethodNotAllowedHandler handles 405 responses.
type MethodNotAllowedHandler Handler
Testing with httptest
Kite implements http.Handler via ServeHTTP, so you can test your routes without binding a real port:
k := kite.New()
k.GET("/hello", func(ctx *kite.Context) error {
return ctx.WriteString(200, "Hello, World!")
})
ts := httptest.NewServer(k) // no goroutines, no ports, no polling
defer ts.Close()
resp, _ := http.Get(ts.URL + "/hello")
// assert on resp.StatusCode, body, etc.
---
## License
MIT — see [LICENSE](./LICENSE) for details.