Releases: gofr-dev/gofr
v1.56.7
Release v1.56.7
⚡ Performance — HTTP Hot Path
Reduced per-request allocations and CPU across the pkg/gofr request lifecycle, closing most of the gap to the net/http router cluster.
Benchmark setup: Apple M4, GOMAXPROCS=4, payloads: plaintext (13 B), json (small object), param (single path param). Numbers are machine-specific and will differ on Linux/x86, in containers with CPU limits, and at higher core counts — treat as relative, not absolute.
End-to-end RPS (vs v1.56.6):
| Endpoint | v1.56.6 | v1.56.7 | Δ |
|---|---|---|---|
| plaintext | 89,835 | 133,304 | +48 % |
| json | 89,177 | 118,513 | +33 % |
| param | 87,224 | 121,159 | +39 % |
Per-request profile (pprof):
| Metric | v1.56.6 | v1.56.7 | Δ |
|---|---|---|---|
| Allocations / req | 132 KB | 60 KB | −54 % |
| CPU / req | 33.5 µs | 21 µs | −37 % |
| p99 latency (plaintext) | 3.7 ms | 1.7 ms | −54 % |
What changed
| Area | Change | Impact |
|---|---|---|
| OTel hot path | When TRACE_EXPORTER is unset, installs a NeverSample() TracerProvider; otel.Tracer(...) handle cached in the middleware closure |
Spans keep unique TraceID/SpanID for log correlation but short-circuit at the sampler — 528 B → 144 B / req |
| Middleware | StatusResponseWriter shared between Logging and Metrics and pooled; attribute slices cached per (route, method); time.Now() deferred past probe/GraphQL filters |
Fewer allocations per request |
| Response & routing | Single Write with a Content-Length header (was 2–3 Writes); path.Clean skipped on already-canonical URLs; handler runs inline (no goroutine) when there's no request timeout and the request isn't a WebSocket upgrade |
Lower CPU and allocations on the response path |
🛠️ Fixes
🔹 Tracing spans
HTTP semconv attributes (http.method, http.route, http.status_code) are now recorded natively on the request span, and the redundant per-route otelhttp.NewHandler wrap has been removed.
- Eliminates the duplicate span emitted per request (2 spans/req → 1).
- Span name corrected from the generic
gofr-routerto the standard<METHOD> <route-template>(e.g.GET /users/{id}), matching OTel HTTP semantic conventions.
Note on tracing defaults: the
NeverSample()provider only applies whenTRACE_EXPORTERis unset — i.e. when tracing isn't configured at all. If you setTRACE_EXPORTER, sampling and export behave as before. No configured setup is affected.
Full Changelog: v1.56.6...v1.56.7
v1.56.6
Release v1.56.6
🛠️ Fixes
-
Crontab Goroutine Leak on Shutdown — The cron scheduler's ticker goroutine ran an unbounded
for range c.ticker.Cloop with no stop signal, so it leaked on everyApp.Shutdown(). Added adonechannel and aStop()method guarded bysync.Once, and the goroutine nowselects between the ticker anddoneto exit cleanly.App.Shutdown()now stops the cron scheduler before closing the container, so scheduled jobs are no longer torn down out of order. (#3292) -
Circuit Breaker Read-Lock & Non-Blocking Recovery —
circuitBreaker.isOpen()took a full writeLock()for a read-only state check, andtryCircuitRecovery()held the lock across the entirehealthCheckcall — blocking all other requests for the duration of the health probe.isOpen()now usesRLock(), and recovery releases the lock before running the health check, re-acquiring it only to reset the circuit (and only if it's still in the open state). (#3255, #3427) -
Subscriber Commits Messages After Handler Panic — When a subscription handler panicked,
panicRecoveryswallowed the panic and lefterrasnil, so the message fell through tomsg.Commit()and was acknowledged despite never being processed — causing silent message loss. The recovery path now sets a sentinel error and returns before the commit, so a panicked message is left uncommitted and will be redelivered. (#3424) -
Go 1.26 — Bumped Go to
1.26across all modules and CI workflows. (#3426) -
Dependency Updates — Consolidated minor and patch dependency updates across the core module and datasource sub-modules. (#3423)
v1.56.5
Release v1.56.5
🛠 Fixes
-
gRPC Shutdown Nil Panic — The force-close fallback in
grpcServer.Shutdown(invoked when the graceful context times out) didn't nil-checkg.server. Apps that never calledRegisterServicepanicked on SIGTERM during the grace period. Added the missing nil-check and a regression test. (#3368) -
MongoDB / ArangoDB Operation Metrics —
defer sendOperationStats(time.Now(), …)evaluatedtime.Now()at defer registration and then calledtime.Since(time.Now())
internally, so every op reported ~0 µs duration. A few sites also placed thedeferafter an early-return, dropping metrics on failing ops. Replaced with aninstrumentOphelper that captures start time synchronously and always fires its cleanup, so durations are accurate and error paths still record histograms. ArangoDBDropCollection/
TruncateCollectionnow also time the actual op rather than just thegetCollection()lookup. (#3102) -
DBResolver Tracing —
AddDBResolverpreviously dropped tracing silently because no matcher arm existed for it. Tracing is now wired, and a warning is emitted when a datasource implementsUseTracerwithout a matcher entry. (#3102) -
Duck-typed datasource wiring —
AddMongo,AddArangoDB,AddClickhouse,AddCassandra,AddPubSub, and the rest of theAddXxxfamily now accept the underlying behavior interface directly. The*Providershims inpkg/gofr/container/datasources.goare markedDeprecatedand retained for backwards compatibility — existing code continues to compile. (#3102)
v1.56.4
Release v1.56.4
🛠️ Fixes
-
Kafka Stale Admin Connection — Kafka subscribers got permanently stuck on
"kafka client not connected"after the broker closed an idle TCP connection (defaultconnections.max.idle.ms = 10m). The adminmultiConnwas dialed only once duringinitialize()andretryConnect()only covered initial startup, so everySubscribe/Querycall failed for the rest of the process lifetime. AddedensureConnected(ctx)which detects a stale admin conn at the entry ofSubscribe/Query, re-dials the broker list, and swaps in a freshmultiConnwhile leaving the writer and per-topic readers untouched so in-flight work isn't disrupted. No public API change. -
SwaggerUI Panic on Files Without Extension —
SwaggerUIHandlerpanicked with an index out of bounds error when serving filenames that had no extension, due tostrings.Split+ direct index access on the result. Replaced withfilepath.Extfor safe extraction, and the handler now returns a definederrMissingFileExtensioninstead of panicking.
v1.56.3
Release v1.56.3
🛠️Fixes
- Docs Build Prerender Crash — Fixed an unclosed dotenv code block in the Pub/Sub advanced guide (
/docs/advanced-guide/using-publisher-subscriber) that causediNext.jsftort throwTypeError: Cannot read properties of undefined (reading 'toLowerCase')during prerendering, breaking theDockerizestep of the v1.56.2 release workflow.
v1.56.2
Release v1.56.2
🛠️Fixes
Consumer spans are now real children of the producer's trace across all Pub/Sub backends — Kafka, NATS, Google Pub/Sub, and SQS — while still preserving the existing OTel span link for fan-out semantics.
Previously, each consumer span started from a fresh root context, so an end-to-end publish/subscribe flow appeared as multiple disconnected traces in Jaeger, Tempo, andtracer.gofr.dev. Now the consumer span inherits the producer's trace ID and parent, giving you a single connected trace across all services with no extra configuration.
- Fan-out preserved — span link is still attached for tools that surface fan-out semantics
- Applies to: Kafka, NATS, Google Pub/Sub, SQS
v1.56.1
🛠️ Fixes
-
Redis Streams PubSub CPU Busy-Spin — When the message buffer was full,
subscribeToStreamlooped at 100% CPU with no backoff becauseconsumeStreamMessagesreturned immediately on zero capacity. Added a
time.Afterbackoff using the existingsubscribeRetryInterval, following the same pattern used in the Subscribe connection-wait loop. -
Redis Streams Goroutine Leak on Shutdown — The subscription context created in
ensureSubscriptionwas discarded (_, cancel := ...) andrunSubscriptionLoopused a freshcontext.Background(), making
goroutines impossible to cancel. The cancelable context is now propagated through the full call chain, andClose()no longer holds the write lock while waiting for goroutines (which previously caused a
guaranteed 5-second deadlock on every shutdown). -
Redis Streams Reconnection Failure —
resubscribeAllwas silently a no-op for streams — it canceled a context nobody held and never started a new goroutine. It now properly waits for old goroutines to exit
and starts new ones with fresh contexts, preserving the consumer name across resubscribes to maintain PEL ownership and prevent message loss.
v1.56.0
Release v1.56.0
🚀 Features
🔹 CORS Multiple Origins Support
GoFr's CORS middleware now supports multiple allowed origins, enabling fine-grained access control for applications serving multiple frontends or admin panels.
- Comma-Separated Origins: Configure multiple origins via the
ACCESS_CONTROL_ALLOW_ORIGINenvironment variable - Dynamic Origin Matching: On each request, the
Originheader is matched against the allowed set and only the matched origin is returned in the response (per HTTP spec) - Correct Caching: Adds
Vary: Originheader when a specific origin is matched, ensuring proxies and CDNs cache responses correctly - Fully Backward-Compatible: Single origin and wildcard (
*) work exactly as before
Configuration Example
# Single origin (existing behavior)
ACCESS_CONTROL_ALLOW_ORIGIN=https://app.example.com
# Multiple origins (new)
ACCESS_CONTROL_ALLOW_ORIGIN=https://app.example.com,https://admin.example.com🔧 Enhancements
🔹 Selective Migration Record Tracking
Migrations now only insert gofr_migrations records into databases that were actually used by the UP function, instead of all connected databases. Previously, running a migration that only touched MySQL would still insert migration records into Redis, MongoDB, Cassandra, and every other connected database — leading to phantom records, wasted I/O, and a misleading audit trail. Each datasource is now wrapped with lightweight usage-tracking before UP() executes, and only touched databases receive the migration record.
🛠️ Fixes
-
WebSocket Nil Pointer Panic — Fixed a nil pointer dereference in
WriteMessageToSocketwhen no WebSocket connection exists in the context. It now returnsErrConnectionNotFoundinstead of panicking. -
File Logger Descriptor Leak —
NewFileLogger()opens a file viaos.OpenFilebut previously had no way to close it, causing file descriptor leaks. Added aClose()method to the logger, which is now called duringApp.Shutdown()(HTTP/gRPC) and aftercmd.Run()(CLI apps).Fatal/Fatalfalso now callfile.Sync()before exiting to flush buffered log data. -
Cron Job Duration Log Format — Fixed
%!s(float64=...)in cron job completion logs by keepingdurationastime.Durationinstead of converting tofloat64. Logs now show human-readable durations (e.g.,25.071286msinstead of%!s(float64=0.025071286)). -
UpdateOne Breaking Change Reverted — Reverted the
Mongointerface'sUpdateOnereturn type from(int64, error)back toerror, fixing a breaking change introduced in v1.55.0. The migration lock refresh now usesUpdateMany(which already returns the neededModifiedCount) with a filter that guarantees at most one document matches. -
Dependency Updates — Consolidated minor dependency updates and bumped
github.com/go-jose/go-jose/v4for security improvements.
v1.55.0
Release v1.55.0
🚀 Features
Native GraphQL Support
GoFr now provides first-class GraphQL support using a schema-first approach. Define your API contract in ./configs/schema.graphqls, register resolvers with app.GraphQLQuery() and app.GraphQLMutation(), and GoFr handles execution, validation, tracing, and metrics automatically.
- Single Endpoint: All operations served at
POST /graphql - Interactive Playground: Auto-hosted at
/.well-known/graphql/ui - Full Observability: Per-resolver OpenTelemetry tracing, operation-level metrics (
app_graphql_operations_total,app_graphql_error_total,app_graphql_request_duration), and structured logs aligned with GoFr's existing format - Operation Name Extraction: Automatically parses resolver names from the query AST for meaningful metric labels, even for anonymous operations
- Grafana Dashboard: GraphQL panels added to the standard GoFr dashboard with Response Time SLA, Request/Error Count, and Latency breakdowns by operation and type
gRPC Rate Limiter
Built-in rate limiter interceptors for gRPC using a token bucket algorithm, supporting both unary and streaming RPCs.
- Per-IP Rate Limiting: Each client IP gets its own rate limit bucket
- Shared or Separate Budgets: Use a single store for combined limits or independent configs per call type
- Prometheus Metrics: Track violations via
app_grpc_rate_limit_exceeded_totalcounter - Returns
RESOURCE_EXHAUSTEDwithretry-aftermetadata on limit exceeded
GCS Metadata Upload and Signed URL Generation
Google Cloud Storage now supports cloud-specific operations beyond the standard filesystem interface.
CreateWithOptions: Upload files with customContent-Type,Content-Disposition, and arbitrary metadataGenerateSignedURL: Create time-limited, pre-authenticated download URLsfile.AsCloud(): Safe type assertion helper to check cloud support
🔧 Enhancements
NATS OpenTelemetry Span Links
Added span link support for NATS JetStream PubSub operations, providing distributed tracing visibility into publish, subscribe, and commit operations.
gRPC Observability Improvements
Client-side errors (e.g., InvalidArgument, NotFound, ResourceExhausted) are no longer logged at ERROR level — only server-side errors are. This reduces log noise in production.
🛠️ Fixes
-
Migration Race Condition — Fixed a race condition where multiple pods starting simultaneously could execute the same migration concurrently. The migration state is now re-verified under the distributed lock before execution, preventing duplicate runs in multi-instance deployments.
-
Zipkin Deprecation Warning —
TRACE_EXPORTER=zipkinnow logs an actionable deprecation warning with migration instructions to OTLP (Zipkin supports OTLP natively since v2.24+). -
CI Workflow Updates — Bumped Docker GitHub Actions (
setup-buildx-action,login-action) from v3 to v4 in website deployment workflows.
v1.54.6
Release v1.54.6
🛠️ Fixes
-
PubSub Migration Conflict Resolution
Resolved an issue where the PubSub Migrator, particularly with Redis Streams, incorrectly reported migration versions based on historical data on the message bus.
- Decoupled Versioning: Made PubSub non-authoritative for migration state by removing it from the version chain. Migration tracking is now strictly confined to primary data stores that support atomicity and locking.
- PubSub Still Available: The PubSub client remains fully accessible in the
migration.Datasourceobject for use in migrationUPfunctions (e.g.,d.PubSub.CreateTopic()).