Release v1.49.0
🚀 New Features
🔹 DB Resolver for MySQL Read/Write Splitting
GoFr introduces a powerful, production-ready DBResolver module that enables automatic read/write splitting between a primary database and multiple replicas.
Key Highlights:
-
Automatic routing based on HTTP methods:
- Writes (
POST,PUT,PATCH,DELETE) → Primary - Reads (
GET,HEAD,OPTIONS) → Replicas
- Writes (
-
Route-based overrides using
PrimaryRoutesfor strongly consistent paths -
Pluggable read-balancing strategies:
- Round-robin
- Random
-
Built-in circuit breaker for tracking replica failures & recovery
-
Full integration through
dbresolver.InitDBResolver() -
Unlocks horizontal scaling for read-heavy services
-
Significantly reduces read latency
-
Ensures high resilience with automatic failover to primary when replicas are down
Additional Details:
Installation
go get gofr.dev/pkg/gofr/datasource/dbresolver@latestInitializing DBResolver
import (
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/datasource/dbresolver"
)
func main() {
a := gofr.New()
err := dbresolver.InitDBResolver(a, &dbresolver.Config{
Strategy: dbresolver.StrategyRoundRobin,
ReadFallback: true, // fallback to primary if all replicas fail
MaxFailures: 3, // mark replica down after 3 failures
TimeoutSec: 30, // cooldown before retrying
PrimaryRoutes: []string{
"/admin",
"/api/payments/*",
},
Replicas: []dbresolver.ReplicaCredential{
{Host: "localhost:3307", User: "replica_user1", Password: "pass1"},
{Host: "replica2.example.com:3308", User: "replica_user2", Password: "pass2"},
{Host: "replica3.example.com:3309", User: "replica_user3", Password: "pass3"},
},
})
if err != nil {
a.Logger().Errorf("failed to initialize db resolver: %v", err)
}
a.Run()
}Read Endpoint (Auto → Replica)
a.GET("/customers", func(c *gofr.Context) (any, error) {
var out []Customer
c.SQL.Select(c, &out, "SELECT id, name FROM customers")
return out, nil
})Write Endpoint (Auto → Primary)
a.POST("/customers", func(c *gofr.Context) (any, error) {
var cust Customer
c.Bind(&cust)
_, err := c.SQL.Exec("INSERT INTO customers (name) VALUES (?)", cust.Name)
return cust, err
})Forced Primary Route
a.GET("/admin/customers", func(c *gofr.Context) (any, error) {
var out []Customer
c.SQL.Select(c, &out, "SELECT id, name FROM customers")
return out, nil
})🔹 XML Responses (response.XML)
GoFr now supports raw XML responses without JSON encoding by using response.XML.
Key Highlights:
- Writes raw XML bytes directly to the client
- Default
Content-Type = application/xml - Perfect for legacy and enterprise XML-based systems
Example:
app.GET("/legacy/xml", func(ctx *gofr.Context) (any, error) {
content := []byte(`<Response status="ok"><Message>Hello</Message></Response>`)
return response.XML{Content: content}, nil
})XML Output
<Response status="ok"><Message>Hello</Message></Response>📦 Improvements
🔹 Responder Improvements
- Special response types (
Raw,File,Template,XML,Redirect) now follow a unified handling flow - Avoids incorrect
206 Partial Contentresponses - Ensures default JSON
Content-Type
🔹 DBResolver Pooling Controls
Replica pools now scale relative to primary pools. Defaults:
- Primary:
DB_MAX_IDLE_CONNECTION=2→ Replicas default to8 - Primary:
DB_MAX_OPEN_CONNECTION=20→ Replicas default to40
Optional overrides:
DB_REPLICA_MAX_IDLE_CAP=100
DB_REPLICA_MIN_IDLE=5
DB_REPLICA_DEFAULT_IDLE=15
DB_REPLICA_MAX_OPEN_CAP=500
DB_REPLICA_MIN_OPEN=20
DB_REPLICA_DEFAULT_OPEN=150🐛 Bug Fixes & Small Changes
🔹 Authentication Timing Attack Protection
- API Key and Basic Auth now use consistent time comparisons
Refer : GoFr Official Documentation
https://gofr.dev/docs