I used Gin for years. Then my API grew, and things got messy.
Let me tell you why I built Rivaas. This isn't about saying other frameworks are bad. They're great. But I needed something different.
The Problem
Three years ago, I started a simple API. Just a few endpoints. I picked Gin because it was fast and popular.
The API worked well. Then the project grew.
Observability Was Bolted On
We needed to track metrics. I added Prometheus manually. Connected it to the routes. Added custom middleware.
Then we needed tracing. I added Jaeger. More middleware. More manual work.
Then we needed structured logging. Another library. More integration code.
Each piece worked. But nothing talked to each other. Logs didn't include trace IDs. Metrics didn't match route names. Every new feature meant writing more glue code.
Configuration Was Scattered
Environment variables were everywhere:
port := os.Getenv("PORT")
dbHost := os.Getenv("DB_HOST")
logLevel := os.Getenv("LOG_LEVEL")
// ... 30 more lines ...
Some config came from files. Some from env vars. Some was hardcoded. When you needed to change something, you had to search through multiple files.
No Lifecycle Management
When we shut down the server, we had to remember to:
- Stop accepting new requests
- Wait for current requests to finish
- Close database connections
- Flush metrics
- Close tracing exporters
This was all manual. Miss one step and you lose data or get errors.
Validation Was Manual
Every endpoint needed validation code:
if user.Email == "" {
return errors.New("email required")
}
if !isValidEmail(user.Email) {
return errors.New("invalid email")
}
if user.Age < 18 {
return errors.New("must be 18 or older")
}
Multiply this by 50 endpoints. That's a lot of boring code.
The Name: Wild Rhubarb
I needed a name for this project. I wanted something meaningful.
I thought about what I wanted the framework to be. Then I remembered ریواس (Rivās) - wild rhubarb.
This plant grows in the mountains of Iran. At 1,500 to 3,000 meters altitude. The weather is harsh. The soil is poor. Few plants can survive there.
But Rivās thrives. It has four special qualities:
1. Resilient
Rivās survives freezing winters and hot summers. It handles extreme conditions.
Your API needs to be resilient too. It should handle panics gracefully. Shut down properly. Recover from errors.
Rivaas includes panic recovery, graceful shutdown, and health checks. Your service stays up even when things go wrong.
2. Lightweight
Rivās doesn't need much. Poor soil is fine. Little water is enough.
Your framework should be the same. It shouldn't use tons of memory. It shouldn't slow down your app.
Rivaas uses 16 bytes per request. It handles 8.4 million requests per second. You don't need huge servers to run it.
3. Adaptive
Rivās grows at different altitudes. In valleys and on peaks. It adapts to its environment.
Your API runs in different places too. Your laptop. A container. A Kubernetes cluster.
Rivaas works everywhere. Same code, different environments. It detects what's available and adapts.
4. Self-Sufficient
Rivās doesn't depend on other plants. It grows on its own.
Your framework should include what you need. Not force you to find and connect dozens of libraries.
Rivaas includes metrics, tracing, logging, validation, and config management. Everything talks to each other. You don't write glue code.
These four qualities guide every decision in Rivaas.
What I Wanted
I sat down and made a list. What would my ideal framework look like?
Batteries Included, But Not Locked In
Most frameworks are either bare minimum or all-in.
Bare minimum frameworks give you routing. You add everything else yourself.
All-in frameworks give you everything. But you can't swap parts out.
I wanted both. Give me good defaults. But let me replace anything.
Rivaas works like this:
// Use the full framework
a, err := app.New(
app.WithObservability(
app.WithMetrics(),
app.WithTracing(),
),
)
Or use just the parts you need:
// Use just the router
r := router.New()
// Add your own metrics library
// Add your own tracing
// Full control
Each package has its own go.mod. You can use one without the others.
Observability Built In, Not Added Later
Metrics, tracing, and logging should be first-class features. Not afterthoughts.
In Rivaas, observability is integrated:
- Logs include trace IDs automatically
- Metrics use the same service name as traces
- Everything is configured in one place
- It all works together
You turn it on with one option. You don't write integration code.
Production-Ready Defaults
Most frameworks give you dev-friendly defaults. Then you search for "production configuration" and copy code from Stack Overflow.
Rivaas has production-ready defaults:
- Graceful shutdown with timeout
- Health endpoints for Kubernetes
- Panic recovery middleware
- Structured JSON logging
- Request timeouts
You can override anything. But the defaults work in production.
Clear Error Messages
When something goes wrong, the error should tell you exactly what happened.
Bad error: validation failed
Good error:
{
"status": 400,
"title": "Validation Failed",
"detail": "The request body contains invalid data",
"errors": [
{
"field": "email",
"message": "must be a valid email address",
"value": "not-an-email"
},
{
"field": "age",
"message": "must be at least 18",
"value": 15
}
]
}
Rivaas gives you the second type. Your frontend developers will thank you.
Design Decisions
I made some choices early on. These shaped how Rivaas works.
Modular Architecture
Each package is independent. Each has its own go.mod file.
rivaas/
├── app/ → rivaas.dev/app
├── router/ → rivaas.dev/router
├── binding/ → rivaas.dev/binding
├── validation/ → rivaas.dev/validation
├── config/ → rivaas.dev/config
├── logging/ → rivaas.dev/logging
├── metrics/ → rivaas.dev/metrics
├── tracing/ → rivaas.dev/tracing
└── ...
You install what you need:
# Full framework
go get rivaas.dev/app
# Just the router
go get rivaas.dev/router
# Just config management
go get rivaas.dev/config
Functional Options Pattern
Every package uses the same configuration pattern:
r := router.New(
router.WithPort(8080),
router.WithTimeout(30 * time.Second),
)
This makes the API consistent. Once you learn it in one package, you know it everywhere.
The App Package as Integration Layer
The app package doesn't have much code. It's mostly integration.
It takes all the other packages and connects them:
- Sets the service name everywhere
- Connects logs to traces
- Sets up health checks
- Manages lifecycle
You can use app for convenience. Or skip it and wire things yourself.
What Makes It Different
There are many Go frameworks. Why choose Rivaas?
Here's an honest comparison:
| Feature | Rivaas | Gin | Echo | Chi |
|---|---|---|---|---|
| Router speed | ⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡⚡ | ⚡⚡ |
| Built-in observability | ✅ | ❌ | ❌ | ❌ |
| Automatic OpenAPI | ✅ | ❌ | ❌ | ❌ |
| Graceful shutdown | ✅ | ⚠️ | ⚠️ | ⚠️ |
| Health endpoints | ✅ | ❌ | ❌ | ❌ |
| Modular packages | ✅ | ❌ | ❌ | ✅ |
| Validation strategies | 3 | 1 | 1 | 0 |
| Config management | ✅ | ❌ | ❌ | ❌ |
(✅ = built-in, ⚠️ = basic, ❌ = not included)
Rivaas isn't always better. But it includes more out of the box.
When to Use Rivaas
Choose Rivaas if you:
- Want observability without setup
- Need automatic API documentation
- Build cloud-native services
- Want production-ready defaults
- Like modular design
When Not to Use Rivaas
Don't choose Rivaas if you:
- Need the absolute smallest binary
- Want to control every detail
- Already have a working setup with another framework
- Prefer older, more stable frameworks
Be honest with yourself about your needs.
The Journey
Building Rivaas took time. Here's what I learned.
Start Small
I didn't build everything at once. I started with the router. Made it fast. Then added binding. Then validation.
Each piece got attention. Nothing was rushed.
Listen to Users
Early users had good ideas. They found bugs. They asked for features I hadn't thought about.
The OpenAPI generation came from user requests. So did the multiple validation strategies.
Make Hard Choices
I wanted to support everything. But that's impossible.
We dropped some features. We said no to some requests. This kept the codebase clean.
Every feature has a maintenance cost. We only add features that are worth it.
Document Everything
Code without docs is useless. I wrote guides for every package. Examples for every feature.
Good documentation takes longer than code. But it's worth it.
What's Next
Rivaas is ready for production. But there's more to do.
Community Growth
We need more contributors. More examples. More tutorials.
If you use Rivaas, share your experience. Write about it. Help others.
More Integrations
We want to integrate with more tools:
- More database helpers
- More auth strategies
- More deployment examples
Performance Improvements
8.4 million requests per second is good. But we can do better.
We're always looking for optimizations. Small gains add up.
Stability and Polish
Every release makes Rivaas more stable. We fix bugs fast. We improve APIs based on feedback.
The goal is a framework you can trust in production.
How to Contribute
Want to help? Here's how:
Use Rivaas
The best way to help is to use it. Build something. Find bugs. Share feedback.
Write About It
Write a blog post. Make a video. Share on social media.
Help others discover Rivaas.
Contribute Code
Check the issues on GitHub. Look for "good first issue" tags.
Write tests. Fix bugs. Add features.
Improve Documentation
Found a confusing doc? Fix it. Missing an example? Add it.
Documentation improvements are always welcome.
Conclusion
I built Rivaas because I needed it. Maybe you need it too.
It's not perfect. No framework is. But it solves real problems.
It gives you:
- Speed without complexity
- Features without bloat
- Flexibility without chaos
- Production-ready without configuration hell
Try it in your next project. See if it fits your needs.
If it does, great. If not, that's fine too. Choose what works for you.
Get Started: go get rivaas.dev/app
Read the Docs: rivaas.dev/docs
View the Code: github.com/rivaas-dev/rivaas
Join Discussions: GitHub Discussions

Top comments (0)