<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohammad Abdolirad</title>
    <description>The latest articles on DEV Community by Mohammad Abdolirad (@atkrad).</description>
    <link>https://dev.to/atkrad</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F548946%2Fcddb4340-65c7-4f75-9a6e-8a589630412a.jpeg</url>
      <title>DEV Community: Mohammad Abdolirad</title>
      <link>https://dev.to/atkrad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atkrad"/>
    <language>en</language>
    <item>
      <title>Go 1.28 HTTP/2 Parking: Measuring Memory Savings</title>
      <dc:creator>Mohammad Abdolirad</dc:creator>
      <pubDate>Mon, 26 Oct 2026 21:46:12 +0000</pubDate>
      <link>https://dev.to/atkrad/go-128-http2-parking-measuring-memory-savings-4jo7</link>
      <guid>https://dev.to/atkrad/go-128-http2-parking-measuring-memory-savings-4jo7</guid>
      <description>&lt;p&gt;While reading the draft Go 1.28 release notes, I noticed a change in the HTTP/2 server. With this change, the goroutine responsible for serving a connection can exit when there is no work to do, instead of waiting. A new goroutine takes over when needed. At first, it seemed like a small change, but I wondered how much memory it could save on a server with thousands of open connections.&lt;/p&gt;

&lt;p&gt;I decided to measure it rather than guess. I wanted to see how it affected process memory usage, goroutine counts, and Go runtime memory metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is HTTP/2 parking?
&lt;/h2&gt;

&lt;p&gt;An HTTP/2 connection can stay open even when it has no active streams. In the older server behavior, a goroutine runs the connection's &lt;code&gt;serveLoop&lt;/code&gt; and waits there when nothing new is ready. The Go 1.28 development code adds an internal mechanism called &lt;em&gt;parking&lt;/em&gt;. When &lt;code&gt;serveLoop&lt;/code&gt; has no work ready to process, and the implementation's safety conditions are met, that goroutine can exit. When new work arrives, a new goroutine runs &lt;code&gt;serveLoop&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Parking is not limited to connections with no active streams. A handler can still be running while &lt;code&gt;serveLoop&lt;/code&gt; has nothing immediate to process. If those safety conditions are met, the serving goroutine can be parked. That does not mean every active connection, or every running handler, can be parked.&lt;/p&gt;

&lt;p&gt;The word &lt;em&gt;parking&lt;/em&gt; comes from the Go implementation, including names such as &lt;code&gt;tryPark&lt;/code&gt; and the temporary &lt;code&gt;GODEBUG&lt;/code&gt; setting &lt;code&gt;http2serveparking&lt;/code&gt;. It is not a public API, and the draft release notes do not use this name for the change. The change is how the server manages the goroutine for a connection, not how application code handles requests.&lt;/p&gt;

&lt;p&gt;This also gave me a useful way to test the change. I could run the same Go 1.28 server binary with parking enabled and disabled, instead of relying only on a Go 1.27 versus Go 1.28 comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested it
&lt;/h2&gt;

&lt;p&gt;I used the same benchmark code and machine for three configurations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Go 1.27.2&lt;/strong&gt;, as an older-version reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go 1.28 development build, parking enabled&lt;/strong&gt;, as the default behavior of the tested revision.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same Go 1.28 binary, parking disabled&lt;/strong&gt;, using &lt;code&gt;GODEBUG=http2serveparking=0&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tested Go 1.28 revision was &lt;code&gt;5c51011e82ed754af072baaf4338ff35e76fe705&lt;/code&gt;. The benchmark source was pinned to commit &lt;code&gt;86753bd4d1f79621a4f3c6927c8239419e54bde2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For each configuration, I measured the server with &lt;strong&gt;0, 1,000, and 5,000 idle HTTP/2 connections&lt;/strong&gt;. Each complete run used ten rounds for every case and connection count. I repeated the full experiment three times on the same Linux machine. The benchmark collected short sample windows after the connections had settled, and the published results use medians rather than a single sample.&lt;/p&gt;

&lt;p&gt;The test machine used an Intel Core i7-1265U, Linux 6.18.56, and about 32 GiB of RAM. The benchmark configuration set &lt;code&gt;GOMAXPROCS=12&lt;/code&gt; and &lt;code&gt;GOGC=100&lt;/code&gt;. I kept the natural garbage collection behavior rather than forcing a collection before each measurement.&lt;/p&gt;

&lt;p&gt;I looked at four metrics. &lt;strong&gt;RSS&lt;/strong&gt; measures resident memory for the server process as reported by Linux. &lt;strong&gt;Goroutines&lt;/strong&gt; shows how many goroutines the Go runtime reports. &lt;strong&gt;StackInuse&lt;/strong&gt; describes memory in use by goroutine stacks. &lt;strong&gt;HeapAlloc&lt;/strong&gt; is the amount of allocated heap memory reported by the runtime. These values are related, but they do not measure the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  RSS: the clearest change
&lt;/h2&gt;

&lt;p&gt;The largest difference appeared at 5,000 idle connections. The table below shows median RSS from each complete run, in MiB:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Go 1.27.2&lt;/th&gt;
&lt;th&gt;Go 1.28, parking off&lt;/th&gt;
&lt;th&gt;Go 1.28, parking on&lt;/th&gt;
&lt;th&gt;Saved by parking&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;249.89&lt;/td&gt;
&lt;td&gt;218.28&lt;/td&gt;
&lt;td&gt;129.19&lt;/td&gt;
&lt;td&gt;89.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;247.18&lt;/td&gt;
&lt;td&gt;223.73&lt;/td&gt;
&lt;td&gt;131.11&lt;/td&gt;
&lt;td&gt;92.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;245.60&lt;/td&gt;
&lt;td&gt;217.94&lt;/td&gt;
&lt;td&gt;133.25&lt;/td&gt;
&lt;td&gt;84.70&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compared with the &lt;strong&gt;same Go 1.28 binary with parking disabled&lt;/strong&gt;, enabling parking reduced RSS by &lt;strong&gt;84.7 to 92.6 MiB&lt;/strong&gt;, or about &lt;strong&gt;38.9% to 41.4%&lt;/strong&gt;, across the three runs. At 1,000 idle connections, the reduction was smaller but still clear, at about &lt;strong&gt;15.7 to 16.6 MiB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0adypd6uc8njkutpj5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj0adypd6uc8njkutpj5n.png" alt="Median server RSS at different numbers of idle HTTP/2 connections" width="800" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The zero-connection baseline stayed close to 8.5 to 8.7 MiB across the configurations. I also calculated baseline-adjusted RSS by subtracting each configuration's zero-connection median. That helps show the growth associated with idle connections, but the main comparison above uses the observed process RSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznf4saebczlt7ozj4676.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fznf4saebczlt7ozj4676.png" alt="RSS reduction with parking enabled" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fewer goroutines, less stack memory
&lt;/h2&gt;

&lt;p&gt;The goroutine counts helped explain the shape of the result. With 5,000 idle connections, Go 1.28 reported about &lt;strong&gt;10,007 goroutines with parking disabled&lt;/strong&gt; and &lt;strong&gt;5,007 with parking enabled&lt;/strong&gt;. That is a difference of exactly &lt;strong&gt;5,000 goroutines&lt;/strong&gt; in this benchmark. At 1,000 idle connections, the difference was 1,000 goroutines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6nztac6i92y6h966377b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6nztac6i92y6h966377b.png" alt="Goroutine counts by idle connection count" width="800" height="537"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The stack numbers moved in the same direction. At 5,000 connections, &lt;strong&gt;StackInuse&lt;/strong&gt; was about &lt;strong&gt;99 MiB with parking disabled&lt;/strong&gt; and about &lt;strong&gt;21 MiB with parking enabled&lt;/strong&gt;. The reduction was close to &lt;strong&gt;78 MiB&lt;/strong&gt; in all three complete runs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F403imbpsf14koqi7m0dw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F403imbpsf14koqi7m0dw.png" alt="Stack and heap memory at 5,000 idle connections" width="800" height="576"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This does not mean that every byte saved in RSS came from stacks. RSS includes more than Go stacks, and changes in heap allocation, runtime state, and the operating system can affect it. It does show that the lower goroutine and stack counts are consistent with the parking mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  HeapAlloc did not tell the same simple story
&lt;/h2&gt;

&lt;p&gt;HeapAlloc was less consistent. In some runs, parking enabled used less heap than parking disabled. In another run, it used slightly more. This is a good reminder that one memory metric cannot stand in for another.&lt;/p&gt;

&lt;p&gt;For this experiment, &lt;strong&gt;RSS, goroutine count, and StackInuse&lt;/strong&gt; provide a clearer picture of the difference. I would not describe the change as a fixed heap saving or claim that a certain percentage of total memory always disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of this benchmark
&lt;/h2&gt;

&lt;p&gt;These measurements come from one machine, one benchmark design, and a specific &lt;strong&gt;development revision&lt;/strong&gt; of Go 1.28. They are not results from a final Go 1.28 release. I tested mostly idle connections, not a mix of real traffic, active streams, and application workloads.&lt;/p&gt;

&lt;p&gt;The benchmark kept the Go 1.28 server binary the same when switching parking on and off. That makes the parking comparison more useful, but it still does not remove all system noise. The three full runs show a repeatable direction and a range, not a universal guarantee.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GODEBUG=http2serveparking=0&lt;/code&gt; setting I used for this comparison is temporary and undocumented. I would not treat it as a stable option for future releases.&lt;/p&gt;

&lt;p&gt;I have kept the &lt;a href="https://github.com/atkrad/benchmarks/tree/86753bd4d1f79621a4f3c6927c8239419e54bde2" rel="noopener noreferrer"&gt;benchmark source at a fixed commit&lt;/a&gt; and the &lt;a href="https://github.com/golang/go/commit/5c51011e82ed754af072baaf4338ff35e76fe705" rel="noopener noreferrer"&gt;tested Go revision&lt;/a&gt; linked here so the details can be checked later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this benchmark, HTTP/2 parking made a clear difference in memory usage. With 5,000 idle connections, the same Go 1.28 server binary used around 85 to 93 MiB less RSS when parking was enabled. It also had 5,000 fewer goroutines and used about 78 MiB less stack memory.&lt;/p&gt;

&lt;p&gt;What I found most interesting was how much memory an otherwise quiet connection can keep around. Changing what the server does while it waits can matter as much as optimizing the work it performs.&lt;/p&gt;

&lt;p&gt;I would not apply these numbers to every Go service. Still, for servers that keep many idle HTTP/2 connections open, this is a change worth understanding and measuring with a real workload.&lt;/p&gt;

</description>
      <category>go</category>
      <category>http2</category>
      <category>performance</category>
      <category>benchmarking</category>
    </item>
    <item>
      <title>Why I Built Rivaas: A Go Framework That Grows With You</title>
      <dc:creator>Mohammad Abdolirad</dc:creator>
      <pubDate>Mon, 26 Jan 2026 11:51:17 +0000</pubDate>
      <link>https://dev.to/atkrad/why-i-built-rivaas-a-go-framework-that-grows-with-you-26pg</link>
      <guid>https://dev.to/atkrad/why-i-built-rivaas-a-go-framework-that-grows-with-you-26pg</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzngqk0jjb9srqu7mj25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzngqk0jjb9srqu7mj25.png" alt="Rivaas Logo" width="256" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used Gin for years. Then my API grew, and things got messy.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Three years ago, I started a simple API. Just a few endpoints. I picked Gin because it was fast and popular.&lt;/p&gt;

&lt;p&gt;The API worked well. Then the project grew.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability Was Bolted On
&lt;/h3&gt;

&lt;p&gt;We needed to track metrics. I added Prometheus manually. Connected it to the routes. Added custom middleware.&lt;/p&gt;

&lt;p&gt;Then we needed tracing. I added Jaeger. More middleware. More manual work.&lt;/p&gt;

&lt;p&gt;Then we needed structured logging. Another library. More integration code.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Was Scattered
&lt;/h3&gt;

&lt;p&gt;Environment variables were everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dbHost&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DB_HOST"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logLevel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LOG_LEVEL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// ... 30 more lines ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Lifecycle Management
&lt;/h3&gt;

&lt;p&gt;When we shut down the server, we had to remember to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop accepting new requests&lt;/li&gt;
&lt;li&gt;Wait for current requests to finish&lt;/li&gt;
&lt;li&gt;Close database connections&lt;/li&gt;
&lt;li&gt;Flush metrics&lt;/li&gt;
&lt;li&gt;Close tracing exporters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was all manual. Miss one step and you lose data or get errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation Was Manual
&lt;/h3&gt;

&lt;p&gt;Every endpoint needed validation code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"email required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isValidEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid email"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;18&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"must be 18 or older"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply this by 50 endpoints. That's a lot of boring code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Name: Wild Rhubarb
&lt;/h2&gt;

&lt;p&gt;I needed a name for this project. I wanted something meaningful.&lt;/p&gt;

&lt;p&gt;I thought about what I wanted the framework to be. Then I remembered &lt;strong&gt;ریواس (Rivās)&lt;/strong&gt; - wild rhubarb.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;But Rivās thrives. It has four special qualities:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Resilient
&lt;/h3&gt;

&lt;p&gt;Rivās survives freezing winters and hot summers. It handles extreme conditions.&lt;/p&gt;

&lt;p&gt;Your API needs to be resilient too. It should handle panics gracefully. Shut down properly. Recover from errors.&lt;/p&gt;

&lt;p&gt;Rivaas includes panic recovery, graceful shutdown, and health checks. Your service stays up even when things go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lightweight
&lt;/h3&gt;

&lt;p&gt;Rivās doesn't need much. Poor soil is fine. Little water is enough.&lt;/p&gt;

&lt;p&gt;Your framework should be the same. It shouldn't use tons of memory. It shouldn't slow down your app.&lt;/p&gt;

&lt;p&gt;Rivaas uses 16 bytes per request. It handles 8.4 million requests per second. You don't need huge servers to run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Adaptive
&lt;/h3&gt;

&lt;p&gt;Rivās grows at different altitudes. In valleys and on peaks. It adapts to its environment.&lt;/p&gt;

&lt;p&gt;Your API runs in different places too. Your laptop. A container. A Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Rivaas works everywhere. Same code, different environments. It detects what's available and adapts.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Self-Sufficient
&lt;/h3&gt;

&lt;p&gt;Rivās doesn't depend on other plants. It grows on its own.&lt;/p&gt;

&lt;p&gt;Your framework should include what you need. Not force you to find and connect dozens of libraries.&lt;/p&gt;

&lt;p&gt;Rivaas includes metrics, tracing, logging, validation, and config management. Everything talks to each other. You don't write glue code.&lt;/p&gt;

&lt;p&gt;These four qualities guide every decision in Rivaas.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wanted
&lt;/h2&gt;

&lt;p&gt;I sat down and made a list. What would my ideal framework look like?&lt;/p&gt;

&lt;h3&gt;
  
  
  Batteries Included, But Not Locked In
&lt;/h3&gt;

&lt;p&gt;Most frameworks are either bare minimum or all-in.&lt;/p&gt;

&lt;p&gt;Bare minimum frameworks give you routing. You add everything else yourself.&lt;/p&gt;

&lt;p&gt;All-in frameworks give you everything. But you can't swap parts out.&lt;/p&gt;

&lt;p&gt;I wanted both. Give me good defaults. But let me replace anything.&lt;/p&gt;

&lt;p&gt;Rivaas works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Use the full framework&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithObservability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMetrics&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use just the parts you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Use just the router&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Add your own metrics library&lt;/span&gt;
&lt;span class="c"&gt;// Add your own tracing&lt;/span&gt;
&lt;span class="c"&gt;// Full control&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each package has its own &lt;code&gt;go.mod&lt;/code&gt;. You can use one without the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability Built In, Not Added Later
&lt;/h3&gt;

&lt;p&gt;Metrics, tracing, and logging should be first-class features. Not afterthoughts.&lt;/p&gt;

&lt;p&gt;In Rivaas, observability is integrated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs include trace IDs automatically&lt;/li&gt;
&lt;li&gt;Metrics use the same service name as traces&lt;/li&gt;
&lt;li&gt;Everything is configured in one place&lt;/li&gt;
&lt;li&gt;It all works together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You turn it on with one option. You don't write integration code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production-Ready Defaults
&lt;/h3&gt;

&lt;p&gt;Most frameworks give you dev-friendly defaults. Then you search for "production configuration" and copy code from Stack Overflow.&lt;/p&gt;

&lt;p&gt;Rivaas has production-ready defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Graceful shutdown with timeout&lt;/li&gt;
&lt;li&gt;Health endpoints for Kubernetes&lt;/li&gt;
&lt;li&gt;Panic recovery middleware&lt;/li&gt;
&lt;li&gt;Structured JSON logging&lt;/li&gt;
&lt;li&gt;Request timeouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can override anything. But the defaults work in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clear Error Messages
&lt;/h3&gt;

&lt;p&gt;When something goes wrong, the error should tell you exactly what happened.&lt;/p&gt;

&lt;p&gt;Bad error: &lt;code&gt;validation failed&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Good error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation Failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The request body contains invalid data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"must be a valid email address"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not-an-email"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"must be at least 18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rivaas gives you the second type. Your frontend developers will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Decisions
&lt;/h2&gt;

&lt;p&gt;I made some choices early on. These shaped how Rivaas works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modular Architecture
&lt;/h3&gt;

&lt;p&gt;Each package is independent. Each has its own &lt;code&gt;go.mod&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
└── ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You install what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full framework&lt;/span&gt;
go get rivaas.dev/app

&lt;span class="c"&gt;# Just the router&lt;/span&gt;
go get rivaas.dev/router

&lt;span class="c"&gt;# Just config management&lt;/span&gt;
go get rivaas.dev/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functional Options Pattern
&lt;/h3&gt;

&lt;p&gt;Every package uses the same configuration pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the API consistent. Once you learn it in one package, you know it everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  The App Package as Integration Layer
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;app&lt;/code&gt; package doesn't have much code. It's mostly integration.&lt;/p&gt;

&lt;p&gt;It takes all the other packages and connects them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets the service name everywhere&lt;/li&gt;
&lt;li&gt;Connects logs to traces&lt;/li&gt;
&lt;li&gt;Sets up health checks&lt;/li&gt;
&lt;li&gt;Manages lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use &lt;code&gt;app&lt;/code&gt; for convenience. Or skip it and wire things yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Different
&lt;/h2&gt;

&lt;p&gt;There are many Go frameworks. Why choose Rivaas?&lt;/p&gt;

&lt;p&gt;Here's an honest comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Rivaas&lt;/th&gt;
&lt;th&gt;Gin&lt;/th&gt;
&lt;th&gt;Echo&lt;/th&gt;
&lt;th&gt;Chi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Router speed&lt;/td&gt;
&lt;td&gt;⚡⚡⚡&lt;/td&gt;
&lt;td&gt;⚡⚡⚡&lt;/td&gt;
&lt;td&gt;⚡⚡⚡&lt;/td&gt;
&lt;td&gt;⚡⚡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built-in observability&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automatic OpenAPI&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Graceful shutdown&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health endpoints&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Modular packages&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation strategies&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config management&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(✅ = built-in, ⚠️ = basic, ❌ = not included)&lt;/p&gt;

&lt;p&gt;Rivaas isn't always better. But it includes more out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use Rivaas
&lt;/h3&gt;

&lt;p&gt;Choose Rivaas if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want observability without setup&lt;/li&gt;
&lt;li&gt;Need automatic API documentation&lt;/li&gt;
&lt;li&gt;Build cloud-native services&lt;/li&gt;
&lt;li&gt;Want production-ready defaults&lt;/li&gt;
&lt;li&gt;Like modular design&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When Not to Use Rivaas
&lt;/h3&gt;

&lt;p&gt;Don't choose Rivaas if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need the absolute smallest binary&lt;/li&gt;
&lt;li&gt;Want to control every detail&lt;/li&gt;
&lt;li&gt;Already have a working setup with another framework&lt;/li&gt;
&lt;li&gt;Prefer older, more stable frameworks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be honest with yourself about your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey
&lt;/h2&gt;

&lt;p&gt;Building Rivaas took time. Here's what I learned.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Small
&lt;/h3&gt;

&lt;p&gt;I didn't build everything at once. I started with the router. Made it fast. Then added binding. Then validation.&lt;/p&gt;

&lt;p&gt;Each piece got attention. Nothing was rushed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Listen to Users
&lt;/h3&gt;

&lt;p&gt;Early users had good ideas. They found bugs. They asked for features I hadn't thought about.&lt;/p&gt;

&lt;p&gt;The OpenAPI generation came from user requests. So did the multiple validation strategies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make Hard Choices
&lt;/h3&gt;

&lt;p&gt;I wanted to support everything. But that's impossible.&lt;/p&gt;

&lt;p&gt;We dropped some features. We said no to some requests. This kept the codebase clean.&lt;/p&gt;

&lt;p&gt;Every feature has a maintenance cost. We only add features that are worth it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document Everything
&lt;/h3&gt;

&lt;p&gt;Code without docs is useless. I wrote guides for every package. Examples for every feature.&lt;/p&gt;

&lt;p&gt;Good documentation takes longer than code. But it's worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Rivaas is ready for production. But there's more to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Growth
&lt;/h3&gt;

&lt;p&gt;We need more contributors. More examples. More tutorials.&lt;/p&gt;

&lt;p&gt;If you use Rivaas, share your experience. Write about it. Help others.&lt;/p&gt;

&lt;h3&gt;
  
  
  More Integrations
&lt;/h3&gt;

&lt;p&gt;We want to integrate with more tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More database helpers&lt;/li&gt;
&lt;li&gt;More auth strategies&lt;/li&gt;
&lt;li&gt;More deployment examples&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Improvements
&lt;/h3&gt;

&lt;p&gt;8.4 million requests per second is good. But we can do better.&lt;/p&gt;

&lt;p&gt;We're always looking for optimizations. Small gains add up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stability and Polish
&lt;/h3&gt;

&lt;p&gt;Every release makes Rivaas more stable. We fix bugs fast. We improve APIs based on feedback.&lt;/p&gt;

&lt;p&gt;The goal is a framework you can trust in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Contribute
&lt;/h2&gt;

&lt;p&gt;Want to help? Here's how:&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Rivaas
&lt;/h3&gt;

&lt;p&gt;The best way to help is to use it. Build something. Find bugs. Share feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write About It
&lt;/h3&gt;

&lt;p&gt;Write a blog post. Make a video. Share on social media.&lt;/p&gt;

&lt;p&gt;Help others discover Rivaas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Contribute Code
&lt;/h3&gt;

&lt;p&gt;Check the &lt;a href="https://github.com/rivaas-dev/rivaas/issues" rel="noopener noreferrer"&gt;issues&lt;/a&gt; on GitHub. Look for "good first issue" tags.&lt;/p&gt;

&lt;p&gt;Write tests. Fix bugs. Add features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Improve Documentation
&lt;/h3&gt;

&lt;p&gt;Found a confusing doc? Fix it. Missing an example? Add it.&lt;/p&gt;

&lt;p&gt;Documentation improvements are always welcome.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I built Rivaas because I needed it. Maybe you need it too.&lt;/p&gt;

&lt;p&gt;It's not perfect. No framework is. But it solves real problems.&lt;/p&gt;

&lt;p&gt;It gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speed without complexity&lt;/li&gt;
&lt;li&gt;Features without bloat&lt;/li&gt;
&lt;li&gt;Flexibility without chaos&lt;/li&gt;
&lt;li&gt;Production-ready without configuration hell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it in your next project. See if it fits your needs.&lt;/p&gt;

&lt;p&gt;If it does, great. If not, that's fine too. Choose what works for you.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Get Started&lt;/strong&gt;: &lt;code&gt;go get rivaas.dev/app&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Read the Docs&lt;/strong&gt;: &lt;a href="https://rivaas.dev/docs/" rel="noopener noreferrer"&gt;rivaas.dev/docs&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;View the Code&lt;/strong&gt;: &lt;a href="https://github.com/rivaas-dev/rivaas" rel="noopener noreferrer"&gt;github.com/rivaas-dev/rivaas&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Join Discussions&lt;/strong&gt;: &lt;a href="https://github.com/rivaas-dev/rivaas/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>rivaas</category>
      <category>tutorial</category>
      <category>framework</category>
    </item>
    <item>
      <title>Ensuring Service Availability in Modern Infrastructure with Wait4X's TCP Checker</title>
      <dc:creator>Mohammad Abdolirad</dc:creator>
      <pubDate>Mon, 24 Mar 2025 16:10:13 +0000</pubDate>
      <link>https://dev.to/atkrad/ensuring-service-availability-in-modern-infrastructure-with-wait4xs-tcp-checker-3jg2</link>
      <guid>https://dev.to/atkrad/ensuring-service-availability-in-modern-infrastructure-with-wait4xs-tcp-checker-3jg2</guid>
      <description>&lt;p&gt;Are you tired of race conditions in your deployment pipelines? Ever run into those pesky "connection refused" errors when your application starts before its dependencies? Today I'll introduce you to Wait4X's TCP checker - a powerful tool for ensuring your services are ready before your application tries to connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Wait4X?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wait4x/wait4x" rel="noopener noreferrer"&gt;Wait4X&lt;/a&gt; is a lightweight, zero-dependency tool designed to wait for services to be ready before continuing. It's a Swiss Army knife for service readiness checks, supporting multiple protocols and integrations including TCP, HTTP, DNS, and various databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  The TCP Checker: Simple Yet Powerful
&lt;/h2&gt;

&lt;p&gt;At its core, the TCP checker does one thing extremely well: it attempts to establish a TCP connection to a specified address and port. If the connection succeeds, the service is considered ready; if it fails, Wait4X will retry based on your configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Usage
&lt;/h2&gt;

&lt;p&gt;The simplest way to use Wait4X's TCP checker is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will attempt to connect to MySQL running on the default port, retrying until it succeeds or times out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced Options
&lt;/h2&gt;

&lt;p&gt;Wait4X offers several options to fine-tune TCP checking behavior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Connection timeout: Specify how long to wait for each connection attempt&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:8080 &lt;span class="nt"&gt;--connection-timeout&lt;/span&gt; 5s
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Retry interval: Control how frequently to retry&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:8080 &lt;span class="nt"&gt;--interval&lt;/span&gt; 2s
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exponential backoff: Use smarter retry strategies&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:8080 &lt;span class="nt"&gt;--backoff-policy&lt;/span&gt; exponential &lt;span class="nt"&gt;--backoff-exponential-coefficient&lt;/span&gt; 2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multiple endpoints: Check multiple services in parallel&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:3306 localhost:6379 localhost:27017
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reverse checking: Wait for a port to become free&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wait4x tcp localhost:8080 &lt;span class="nt"&gt;--invert-check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Real-world Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker Compose Orchestration
&lt;/h3&gt;

&lt;p&gt;When working with Docker Compose, you often have dependencies between services. Using Wait4X, you can ensure services start in the right order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:13&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5432:5432"&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./api&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh -c "wait4x tcp db:5432 &amp;amp;&amp;amp; npm start"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;In CI/CD pipelines, Wait4X ensures tests don't run until services are ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start services&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# Wait for services to be ready&lt;/span&gt;
wait4x tcp localhost:3306 localhost:6379

&lt;span class="c"&gt;# Run tests&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Kubernetes Init Containers
&lt;/h3&gt;

&lt;p&gt;Wait4X is perfect for Kubernetes init containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wait-for-db&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wait4x/wait4x:latest&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wait4x"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgres-service:5432"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--timeout"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;60s"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app:latest&lt;/span&gt;
          &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Wait4X as a Go Package
&lt;/h3&gt;

&lt;p&gt;For Go developers, Wait4X provides a clean API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a context with timeout&lt;/span&gt;
&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Create a TCP checker&lt;/span&gt;
&lt;span class="n"&gt;tcpChecker&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tcp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c"&gt;// Wait for the TCP port to be available&lt;/span&gt;
&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;waiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tcpChecker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;waiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;waiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;waiter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBackoffPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"exponential"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to connect: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Service is ready!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How it Works Under the Hood
&lt;/h4&gt;

&lt;p&gt;Wait4X's TCP checker uses Go's net.Dialer.DialContext to attempt TCP connections with configurable timeouts. It handles various error cases intelligently, distinguishing between connection refusals, timeouts, and other network issues.&lt;/p&gt;

&lt;p&gt;The exponential backoff strategy progressively increases the interval between retries, reducing unnecessary connection attempts while still responding quickly when services become available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Wait4X's TCP checker solves a common but critical challenge in modern infrastructure: ensuring services are actually ready before dependent applications attempt to connect. By providing a simple, reliable way to check TCP connectivity, Wait4X helps eliminate race conditions, reduce errors, and make your deployments more robust.&lt;/p&gt;

&lt;p&gt;Give Wait4X a try in your next project - your deployment pipelines will thank you!&lt;/p&gt;




&lt;p&gt;What service readiness problems have you encountered in your infrastructure? Share your experiences in the comments!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>go</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
