DEV Community

Cover image for Why I Built Rivaas: A Go Framework That Grows With You
Mohammad Abdolirad
Mohammad Abdolirad

Posted on

Why I Built Rivaas: A Go Framework That Grows With You

Rivaas Logo

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 ...
Enter fullscreen mode Exit fullscreen mode

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")
}
Enter fullscreen mode Exit fullscreen mode

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(),
    ),
)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
└── ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Functional Options Pattern

Every package uses the same configuration pattern:

r := router.New(
    router.WithPort(8080),
    router.WithTimeout(30 * time.Second),
)
Enter fullscreen mode Exit fullscreen mode

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)