Router
Advanced Features
Explore advanced router features including OpenAPI documentation, AsyncAPI support, WebSocket handling, streaming responses, and more.
Advanced Features
Forge's router provides powerful advanced features for modern web applications, including automatic API documentation generation, real-time communication, and streaming capabilities.
OpenAPI Documentation
Basic OpenAPI Setup
Enable automatic OpenAPI 3.1.0 specification generation:
package main
import (
"github.com/xraph/forge"
)
func main() {
app := forge.New(
forge.WithOpenAPI(forge.OpenAPIConfig{
Title: "My API",
Version: "1.0.0",
Description: "A comprehensive API built with Forge",
Servers: []forge.OpenAPIServer{
{
URL: "https://api.example.com",
Description: "Production server",
},
{
URL: "http://localhost:8080",
Description: "Development server",
},
},
UIPath: "/docs", // Swagger UI path
SpecPath: "/openapi.json", // OpenAPI spec endpoint
}),
)
router := app.Router()
// Routes with OpenAPI documentation
router.POST("/users", createUser,
forge.WithSummary("Create a new user"),
forge.WithDescription("Creates a new user with the provided information"),
forge.WithTags("users"),
forge.WithOperationID("createUser"),
)
app.Start(":8080")
}Advanced OpenAPI Features
// Automatic schema extraction from structs
type User struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
Name string `json:"name" example:"John Doe" validate:"required,min=2,max=100"`
Email string `json:"email" example:"john@example.com" validate:"required,email"`
Age int `json:"age" example:"30" validate:"min=18,max=120"`
Role string `json:"role" example:"user" enums:"user,admin,moderator"`
Active bool `json:"active" example:"true"`
CreatedAt time.Time `json:"createdAt" example:"2024-01-15T10:30:00Z"`
}
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=100"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"min=18,max=120"`
Role string `json:"role" validate:"required,oneof=user admin moderator"`
}
// Handler with automatic schema detection
func createUser(ctx *forge.Context) error {
var req CreateUserRequest
if err := ctx.Bind(&req); err != nil {
return ctx.JSON(400, map[string]string{"error": "Invalid request"})
}
user := User{
ID: generateID(),
Name: req.Name,
Email: req.Email,
Age: req.Age,
Role: req.Role,
Active: true,
CreatedAt: time.Now(),
}
return ctx.JSON(201, user)
}
// Register with comprehensive documentation
router.POST("/users", createUser,
forge.WithSummary("Create a new user"),
forge.WithDescription("Creates a new user with the provided information"),
forge.WithTags("users", "management"),
forge.WithOperationID("createUser"),
forge.WithRequestSchema(&CreateUserRequest{}),
forge.WithResponseSchema(201, "User created successfully", &User{}),
forge.WithErrorResponses(),
)// Add request and response examples
router.POST("/users", createUser,
forge.WithRequestExample("basic", map[string]interface{}{
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"role": "user",
}),
forge.WithRequestExample("admin", map[string]interface{}{
"name": "Admin User",
"email": "admin@example.com",
"age": 35,
"role": "admin",
}),
forge.WithResponseExample(201, "success", map[string]interface{}{
"id": "123e4567-e89b-12d3-a456-426614174000",
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"role": "user",
"active": true,
"createdAt": "2024-01-15T10:30:00Z",
}),
forge.WithResponseExample(400, "validation_error", map[string]interface{}{
"error": "Validation failed",
"details": []map[string]string{
{"field": "email", "message": "Invalid email format"},
{"field": "age", "message": "Must be at least 18"},
},
}),
)// Configure security schemes
app := forge.New(
forge.WithOpenAPI(forge.OpenAPIConfig{
Title: "Secure API",
Version: "1.0.0",
SecuritySchemes: map[string]forge.SecurityScheme{
"bearerAuth": {
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
Description: "JWT token authentication",
},
"apiKey": {
Type: "apiKey",
In: "header",
Name: "X-API-Key",
},
"oauth2": {
Type: "oauth2",
Flows: forge.OAuthFlows{
AuthorizationCode: &forge.OAuthFlow{
AuthorizationURL: "https://auth.example.com/oauth/authorize",
TokenURL: "https://auth.example.com/oauth/token",
Scopes: map[string]string{
"read": "Read access",
"write": "Write access",
"admin": "Admin access",
},
},
},
},
},
}),
)
// Apply security to routes
router.GET("/users", listUsers,
forge.WithSecurity(map[string][]string{
"bearerAuth": {},
}),
)
router.POST("/admin/users", createAdminUser,
forge.WithSecurity(map[string][]string{
"bearerAuth": {"admin"},
"oauth2": {"admin"},
}),
)Polymorphic Types and Discriminators
// Base type
type Animal struct {
Type string `json:"type" discriminator:"true"`
Name string `json:"name"`
}
// Derived types
type Dog struct {
Animal
Breed string `json:"breed"`
}
type Cat struct {
Animal
IndoorOnly bool `json:"indoorOnly"`
}
// Configure discriminator
router.POST("/animals", createAnimal,
forge.WithDiscriminator(forge.DiscriminatorConfig{
PropertyName: "type",
Mapping: map[string]string{
"dog": "#/components/schemas/Dog",
"cat": "#/components/schemas/Cat",
},
}),
)Callbacks and Webhooks
// Define callback configuration
router.POST("/orders", createOrder,
forge.WithCallback(forge.CallbackConfig{
Name: "orderStatusUpdate",
URL: "{$request.body#/webhookUrl}",
Operations: map[string]forge.CallbackOperation{
"post": {
Summary: "Order status update",
Description: "Called when order status changes",
RequestBody: &OrderStatusUpdate{},
Responses: map[string]forge.ResponseSchemaDef{
"200": {Description: "Webhook received"},
"400": {Description: "Invalid webhook payload"},
},
},
},
}),
)
type OrderStatusUpdate struct {
OrderID string `json:"orderId"`
Status string `json:"status"`
UpdatedAt time.Time `json:"updatedAt"`
}AsyncAPI Documentation
WebSocket Documentation
Enable AsyncAPI 3.0.0 specification for real-time APIs:
app := forge.New(
forge.WithAsyncAPI(forge.AsyncAPIConfig{
Title: "Real-time API",
Version: "1.0.0",
Description: "WebSocket and SSE endpoints",
Servers: map[string]forge.AsyncAPIServer{
"websocket": {
URL: "ws://localhost:8080",
Protocol: "ws",
Description: "WebSocket server",
},
"sse": {
URL: "http://localhost:8080",
Protocol: "sse",
Description: "Server-Sent Events",
},
},
UIPath: "/asyncapi",
SpecPath: "/asyncapi.json",
}),
)
// WebSocket endpoint with AsyncAPI documentation
router.WebSocket("/chat", handleChat,
forge.WithAsyncAPISummary("Chat WebSocket"),
forge.WithAsyncAPIDescription("Real-time chat communication"),
forge.WithWebSocketMessages(&ChatMessage{}, &ChatResponse{}),
forge.WithAsyncAPITags("chat", "realtime"),
)
type ChatMessage struct {
Type string `json:"type" example:"message"`
Content string `json:"content" example:"Hello, world!"`
UserID string `json:"userId" example:"user123"`
}
type ChatResponse struct {
Type string `json:"type" example:"message"`
Content string `json:"content" example:"Hello, world!"`
UserID string `json:"userId" example:"user123"`
Timestamp time.Time `json:"timestamp" example:"2024-01-15T10:30:00Z"`
}Server-Sent Events (SSE)
// SSE endpoint with AsyncAPI documentation
router.EventStream("/events", handleEvents,
forge.WithAsyncAPISummary("Event Stream"),
forge.WithAsyncAPIDescription("Server-sent events for real-time updates"),
forge.WithAsyncAPIChannelName("events"),
forge.WithAsyncAPITags("events", "streaming"),
)
func handleEvents(ctx *forge.Context) error {
// Set SSE headers
ctx.SetHeader("Content-Type", "text/event-stream")
ctx.SetHeader("Cache-Control", "no-cache")
ctx.SetHeader("Connection", "keep-alive")
// Create event channel
events := make(chan Event, 10)
// Start event producer
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
events <- Event{
Type: "heartbeat",
Data: map[string]interface{}{
"timestamp": time.Now(),
"status": "alive",
},
}
case <-ctx.Done():
return
}
}
}()
// Stream events
for event := range events {
data, _ := json.Marshal(event.Data)
fmt.Fprintf(ctx.Writer(), "event: %s\ndata: %s\n\n", event.Type, data)
ctx.Writer().Flush()
}
return nil
}
type Event struct {
Type string `json:"type"`
Data interface{} `json:"data"`
}WebSocket Support
Basic WebSocket Handler
func handleWebSocket(ctx *forge.Context) error {
// Upgrade connection to WebSocket
conn, err := ctx.UpgradeWebSocket()
if err != nil {
return err
}
defer conn.Close()
// Handle messages
for {
var msg ChatMessage
if err := conn.ReadJSON(&msg); err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
ctx.Logger().Error("WebSocket error", "error", err)
}
break
}
// Process message
response := ChatResponse{
Type: "response",
Content: fmt.Sprintf("Echo: %s", msg.Content),
UserID: msg.UserID,
Timestamp: time.Now(),
}
// Send response
if err := conn.WriteJSON(response); err != nil {
ctx.Logger().Error("Write error", "error", err)
break
}
}
return nil
}
// Register WebSocket route
router.WebSocket("/ws/chat", handleWebSocket,
forge.WithName("chatWebSocket"),
forge.WithAsyncAPISummary("Chat WebSocket"),
forge.WithWebSocketMessages(&ChatMessage{}, &ChatResponse{}),
)Advanced WebSocket Patterns
// WebSocket room management
type ChatRoom struct {
ID string
clients map[*websocket.Conn]string
mu sync.RWMutex
}
var rooms = make(map[string]*ChatRoom)
var roomsMu sync.RWMutex
func handleChatRoom(ctx *forge.Context) error {
roomID := ctx.Param("roomId")
userID := ctx.Query("userId")
conn, err := ctx.UpgradeWebSocket()
if err != nil {
return err
}
defer conn.Close()
// Join room
room := getOrCreateRoom(roomID)
room.addClient(conn, userID)
defer room.removeClient(conn)
// Handle messages
for {
var msg ChatMessage
if err := conn.ReadJSON(&msg); err != nil {
break
}
// Broadcast to room
room.broadcast(msg)
}
return nil
}
func (r *ChatRoom) addClient(conn *websocket.Conn, userID string) {
r.mu.Lock()
defer r.mu.Unlock()
r.clients[conn] = userID
}
func (r *ChatRoom) removeClient(conn *websocket.Conn) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.clients, conn)
}
func (r *ChatRoom) broadcast(msg ChatMessage) {
r.mu.RLock()
defer r.mu.RUnlock()
for conn := range r.clients {
if err := conn.WriteJSON(msg); err != nil {
conn.Close()
delete(r.clients, conn)
}
}
}
// Register room-based WebSocket
router.WebSocket("/ws/rooms/:roomId", handleChatRoom,
forge.WithAsyncAPISummary("Chat Room WebSocket"),
forge.WithAsyncAPIDescription("Join a chat room for real-time communication"),
)// WebSocket with authentication
func authenticatedWebSocket(ctx *forge.Context) error {
// Authenticate before upgrade
token := ctx.Query("token")
if token == "" {
return ctx.JSON(401, map[string]string{
"error": "Authentication token required",
})
}
user, err := validateToken(token)
if err != nil {
return ctx.JSON(401, map[string]string{
"error": "Invalid token",
})
}
// Upgrade to WebSocket
conn, err := ctx.UpgradeWebSocket()
if err != nil {
return err
}
defer conn.Close()
// Set user context
ctx.Set("user", user)
// Handle authenticated messages
for {
var msg AuthenticatedMessage
if err := conn.ReadJSON(&msg); err != nil {
break
}
// Process with user context
response := processAuthenticatedMessage(msg, user)
conn.WriteJSON(response)
}
return nil
}
type AuthenticatedMessage struct {
Type string `json:"type"`
Payload interface{} `json:"payload"`
}Streaming Responses
Chunked Transfer Encoding
func streamLargeData(ctx *forge.Context) error {
// Set streaming headers
ctx.SetHeader("Content-Type", "application/json")
ctx.SetHeader("Transfer-Encoding", "chunked")
// Start JSON array
ctx.Writer().Write([]byte("["))
// Stream data chunks
for i := 0; i < 1000; i++ {
if i > 0 {
ctx.Writer().Write([]byte(","))
}
item := map[string]interface{}{
"id": i,
"data": fmt.Sprintf("Item %d", i),
"timestamp": time.Now(),
}
data, _ := json.Marshal(item)
ctx.Writer().Write(data)
ctx.Writer().Flush()
// Simulate processing time
time.Sleep(10 * time.Millisecond)
}
// End JSON array
ctx.Writer().Write([]byte("]"))
return nil
}
router.GET("/stream/data", streamLargeData,
forge.WithSummary("Stream large dataset"),
forge.WithDescription("Streams a large dataset using chunked transfer encoding"),
)File Streaming
func streamFile(ctx *forge.Context) error {
filename := ctx.Param("filename")
// Open file
file, err := os.Open(filepath.Join("uploads", filename))
if err != nil {
return ctx.JSON(404, map[string]string{
"error": "File not found",
})
}
defer file.Close()
// Get file info
stat, err := file.Stat()
if err != nil {
return ctx.JSON(500, map[string]string{
"error": "Failed to get file info",
})
}
// Set headers
ctx.SetHeader("Content-Type", "application/octet-stream")
ctx.SetHeader("Content-Length", fmt.Sprintf("%d", stat.Size()))
ctx.SetHeader("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
// Stream file
_, err = io.Copy(ctx.Writer(), file)
return err
}
router.GET("/files/:filename", streamFile,
forge.WithSummary("Download file"),
forge.WithDescription("Streams a file for download"),
forge.WithTags("files"),
)WebTransport Support
WebTransport provides low-latency, bidirectional communication over HTTP/3 with QUIC transport.
// Enable WebTransport
app := forge.New(
forge.WithWebTransport(forge.WebTransportConfig{
Enabled: true,
CertFile: "server.crt",
KeyFile: "server.key",
}),
)
// WebTransport handler
func handleWebTransport(ctx *forge.Context) error {
session, err := ctx.AcceptWebTransport()
if err != nil {
return err
}
defer session.Close()
// Handle bidirectional streams
for {
stream, err := session.AcceptStream()
if err != nil {
break
}
go handleWebTransportStream(stream)
}
return nil
}
func handleWebTransportStream(stream WebTransportStream) {
defer stream.Close()
// Read data
buffer := make([]byte, 1024)
n, err := stream.Read(buffer)
if err != nil {
return
}
// Process and respond
response := fmt.Sprintf("Echo: %s", string(buffer[:n]))
stream.Write([]byte(response))
}
// Register WebTransport endpoint
router.WebTransport("/wt/echo", handleWebTransport,
forge.WithSummary("WebTransport Echo"),
forge.WithDescription("Bidirectional communication over WebTransport"),
)Route Inspection and Debugging
Route Information
// Get all registered routes
routes := router.Routes()
for _, route := range routes {
fmt.Printf("Route: %s %s -> %s\n",
route.Method,
route.Pattern,
route.Name,
)
}
// Find route by name
if route, found := router.RouteByName("createUser"); found {
fmt.Printf("Found route: %s %s\n", route.Method, route.Pattern)
}
// Find routes by tag
userRoutes := router.RoutesByTag("users")
for _, route := range userRoutes {
fmt.Printf("User route: %s %s\n", route.Method, route.Pattern)
}
// Find routes by metadata
apiRoutes := router.RoutesByMetadata("version", "v1")
for _, route := range apiRoutes {
fmt.Printf("API v1 route: %s %s\n", route.Method, route.Pattern)
}Debug Middleware
func debugMiddleware(next forge.HandlerFunc) forge.HandlerFunc {
return func(ctx *forge.Context) error {
start := time.Now()
// Log request
ctx.Logger().Info("Request started",
"method", ctx.Method(),
"path", ctx.Path(),
"ip", ctx.ClientIP(),
"user_agent", ctx.GetHeader("User-Agent"),
)
// Execute handler
err := next(ctx)
// Log response
duration := time.Since(start)
ctx.Logger().Info("Request completed",
"method", ctx.Method(),
"path", ctx.Path(),
"status", ctx.Response().Status(),
"duration", duration,
"error", err,
)
return err
}
}
// Apply debug middleware globally
router.Use(debugMiddleware)Performance Optimization
Connection Pooling
// Configure connection limits for WebSocket
app := forge.New(
forge.WithWebSocketConfig(forge.WebSocketConfig{
MaxConnections: 1000,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
HandshakeTimeout: 10 * time.Second,
CheckOrigin: func(r *http.Request) bool {
// Implement origin checking
return true
},
}),
)Compression
// Enable compression middleware
router.Use(forge.Compression(forge.CompressionConfig{
Level: 6, // Compression level (1-9)
MinLength: 1024, // Minimum response size to compress
Types: []string{
"application/json",
"text/html",
"text/css",
"application/javascript",
},
}))Best Practices
Advanced Features Best Practices
- Documentation: Always document WebSocket and SSE endpoints with AsyncAPI
- Authentication: Implement proper authentication for real-time endpoints
- Rate Limiting: Apply rate limiting to prevent abuse
- Error Handling: Handle connection errors gracefully
- Resource Cleanup: Always clean up connections and resources
- Monitoring: Monitor connection counts and performance metrics
Error Handling for Streaming
func robustWebSocketHandler(ctx *forge.Context) error {
conn, err := ctx.UpgradeWebSocket()
if err != nil {
return err
}
// Set connection limits
conn.SetReadLimit(512)
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
// Ping ticker for keepalive
ticker := time.NewTicker(54 * time.Second)
defer ticker.Stop()
// Error handling
defer func() {
if r := recover(); r != nil {
ctx.Logger().Error("WebSocket panic", "error", r)
}
conn.Close()
}()
// Message handling with error recovery
for {
select {
case <-ticker.C:
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return err
}
default:
var msg interface{}
if err := conn.ReadJSON(&msg); err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
ctx.Logger().Error("WebSocket error", "error", err)
}
return err
}
// Process message safely
if err := processMessage(msg); err != nil {
ctx.Logger().Error("Message processing error", "error", err)
continue
}
}
}
}Next Steps
- Learn about Route Inspection for debugging and monitoring
- Explore Examples for complete implementation patterns
- Check out Controllers for organizing complex applications
- See Middleware for request processing patterns
How is this guide?
Last updated on