<?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: Vahid Vahedi</title>
    <description>The latest articles on DEV Community by Vahid Vahedi (@vahiiiid).</description>
    <link>https://dev.to/vahiiiid</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%2F2761584%2F73dbd816-7f29-47cf-aaea-4dd51943956e.jpg</url>
      <title>DEV Community: Vahid Vahedi</title>
      <link>https://dev.to/vahiiiid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vahiiiid"/>
    <language>en</language>
    <item>
      <title>What Happens When a Refresh Token Gets Stolen? (Most APIs Have No Answer)</title>
      <dc:creator>Vahid Vahedi</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:41:03 +0000</pubDate>
      <link>https://dev.to/vahiiiid/what-happens-when-a-refresh-token-gets-stolen-most-apis-have-no-answer-2f1f</link>
      <guid>https://dev.to/vahiiiid/what-happens-when-a-refresh-token-gets-stolen-most-apis-have-no-answer-2f1f</guid>
      <description>&lt;p&gt;Every "add JWT auth to your API" tutorial stops at the same place: issue a token, check the signature, done. Nobody covers what happens six weeks later, when a support ticket comes in and you can't answer a very simple question — &lt;strong&gt;if a refresh token gets stolen, how would you even know?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grabapi.dev" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt;, a Go REST API boilerplate I maintain, answers that with token &lt;strong&gt;rotation&lt;/strong&gt; and &lt;strong&gt;reuse detection&lt;/strong&gt; (full reference in the &lt;a href="https://grabapi.dev/docs/AUTHENTICATION/" rel="noopener noreferrer"&gt;Authentication docs&lt;/a&gt;). This post walks through how that actually works, with the real implementation. And because I'd rather show engineering honestly than pretend it was flawless from day one, I'm including the part that isn't as flattering: a hardcoded fallback secret that shipped in two tagged releases before a code review caught it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive version, and why it's a liability
&lt;/h2&gt;

&lt;p&gt;The common pattern looks like this: issue a short-lived access token and a long-lived refresh token. When the access token expires, the client trades the refresh token for a new one. Simple — until you ask what happens if that refresh token leaks. A log line, an XSS payload, a MITM'd request on a coffee shop network — refresh tokens are long-lived and typically stored client-side, so they're a real target.&lt;/p&gt;

&lt;p&gt;With the naive version, a stolen refresh token is indistinguishable from the legitimate one. It works until it expires — which, for a refresh token, could be days or weeks. There is no built-in way to tell "the real user" and "whoever stole their token" apart, and no signal that anything went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The answer: rotation + reuse detection
&lt;/h2&gt;

&lt;p&gt;GRAB's refresh tokens are &lt;strong&gt;single-use&lt;/strong&gt;. Every time one is redeemed, it's marked used and a brand-new refresh token is issued in its place — that's rotation. The part that actually matters is what happens if the &lt;em&gt;same&lt;/em&gt; token is redeemed twice: the server treats it as proof of compromise and kills every token descended from it, not just the one that got reused.&lt;/p&gt;

&lt;p&gt;That requires tracking lineage, which is what &lt;code&gt;token_family&lt;/code&gt; is for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;refresh_tokens&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token_hash&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token_family&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;used_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;revoked_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every refresh token belongs to a &lt;code&gt;token_family&lt;/code&gt; — a UUID assigned at login that every rotated descendant inherits. &lt;code&gt;used_at&lt;/code&gt; marks a token as spent. &lt;code&gt;revoked_at&lt;/code&gt; marks it (and by extension, sometimes its whole family) as dead. Three walks through that table tell the whole story:&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%2F70wsu4aj73qh7xij6l3o.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%2F70wsu4aj73qh7xij6l3o.png" alt=" " width="800" height="848"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Login.&lt;/strong&gt; A user authenticates and gets an access token (15 minutes) plus a refresh token — call it Token A — in a new family, F1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Normal refresh.&lt;/strong&gt; The client redeems Token A. The server marks it used and issues Token B, still in family F1. Token A is now permanently spent — it can never be redeemed again, even if it's still technically unexpired.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Reuse.&lt;/strong&gt; Say Token A leaked before step 2 — a browser extension read it out of storage, whatever the vector. An attacker redeems it. But Token A is already marked &lt;code&gt;used_at&lt;/code&gt;. The server doesn't quietly reject this and move on — it treats a second redemption of a spent token as a signal that the family is compromised, and revokes &lt;em&gt;every&lt;/em&gt; token in F1. That includes Token B, which the legitimate client is currently holding and has never misused. The real user gets logged out and has to sign in again — an inconvenience, but a strictly better outcome than an attacker holding a live session indefinitely.&lt;/p&gt;

&lt;p&gt;Here's the actual logic, from &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/blob/main/internal/auth/service.go" rel="noopener noreferrer"&gt;&lt;code&gt;internal/auth/service.go&lt;/code&gt;&lt;/a&gt;:&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;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;RefreshAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refreshToken&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TokenPair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tokenHash&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;HashToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;storedToken&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;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshTokenRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindByTokenHash&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;tokenHash&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="k"&gt;if&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;Is&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="n"&gt;gorm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrRecordNotFound&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="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidToken&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to find refresh token: %w"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;storedToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RevokedAt&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="k"&gt;return&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;ErrTokenRevoked&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storedToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpiresAt&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="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrExpiredToken&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// A token that's already been used is being replayed. Treat the whole&lt;/span&gt;
    &lt;span class="c"&gt;// family as compromised, not just this one token.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;storedToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UsedAt&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="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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshTokenRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RevokeTokenFamily&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;storedToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TokenFamily&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="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to revoke token family: %w"&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="k"&gt;return&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;ErrTokenReuse&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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refreshTokenRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarkAsUsed&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;storedToken&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ID&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="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to mark token as used: %w"&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="c"&gt;// ...issue a new access token + new refresh token in the same family...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details worth calling out, because they're the kind of thing that's easy to get subtly wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokens are never stored raw.&lt;/strong&gt; &lt;code&gt;refresh_tokens.token_hash&lt;/code&gt; holds &lt;code&gt;sha256(token)&lt;/code&gt;, not the token itself — &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/blob/main/internal/auth/refresh_token.go" rel="noopener noreferrer"&gt;&lt;code&gt;HashToken&lt;/code&gt;&lt;/a&gt;. A dump of that table is useless to an attacker without the original random value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MarkAsUsed&lt;/code&gt; is a conditional update, not a read-then-write.&lt;/strong&gt; It's &lt;code&gt;UPDATE ... WHERE id = ? AND used_at IS NULL&lt;/code&gt;, checked by &lt;code&gt;RowsAffected&lt;/code&gt;. Two concurrent requests racing to redeem the same token can't both win — only one update succeeds, which matters because "redeem" is exactly the kind of operation attackers try to race.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The refresh token itself is 256 bits of &lt;code&gt;crypto/rand&lt;/code&gt;&lt;/strong&gt;, base64-encoded — not a JWT. There's no reason to make it self-describing; it's a bearer credential for a database lookup, and an opaque random value is a smaller attack surface than a second signed token format.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The other way in: a secret that didn't need stealing
&lt;/h2&gt;

&lt;p&gt;Rotation and reuse detection answer the question in the title — but they only cover &lt;em&gt;theft&lt;/em&gt;. There's a second way into an account that requires stealing nothing from the user at all: forging the token outright. Access tokens are HMAC-signed JWTs, which means their entire security rests on the signing secret staying secret. In &lt;code&gt;v1.1.0&lt;/code&gt; and &lt;code&gt;v2.0.0&lt;/code&gt;, &lt;code&gt;NewService&lt;/code&gt; had 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="n"&gt;jwtSecret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;jwtSecret&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="n"&gt;jwtSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"default-secret-change-in-production"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The intent was reasonable enough — don't crash a fresh clone that hasn't set &lt;code&gt;JWT_SECRET&lt;/code&gt; yet. The effect was much worse: if a deployment's config validation was ever bypassed or misconfigured, the service would silently sign tokens with a secret sitting in the repo's Git history, in plaintext, for anyone to read. No stolen token required — just the secret every clone of this repo already has.&lt;/p&gt;

&lt;p&gt;A later review caught it, and the fix (&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/pull/107" rel="noopener noreferrer"&gt;#107&lt;/a&gt;) is intentionally boring: fail loudly instead of falling back.&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%2Ftsq4eqjmcflnquimgcwp.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%2Ftsq4eqjmcflnquimgcwp.png" alt=" " width="800" height="496"&gt;&lt;/a&gt;&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;func&lt;/span&gt; &lt;span class="n"&gt;ValidateConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JWTConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&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;cfg&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="k"&gt;return&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;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fatal: JWT configuration is nil"&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;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Secret&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fatal: JWT_SECRET is not set. Generate one with: make generate-jwt-secret"&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="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;32&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fatal: JWT_SECRET must be at least 32 characters (current: %d)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Secret&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="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;NewService&lt;/code&gt; now panics on an invalid config instead of constructing a service that would sign tokens no attacker even needs to steal. The lesson generalizes past this one repo: a "helpful" default for a security-critical secret isn't helpful, it's a silent single point of failure. If a value has to be secret, the only acceptable behavior for "it's missing" is to refuse to start — never to substitute something that works.&lt;/p&gt;

&lt;p&gt;The 32-character minimum and the &lt;code&gt;make generate-jwt-secret&lt;/code&gt; command are documented in the &lt;a href="https://grabapi.dev/docs/SECURITY_GUIDE/" rel="noopener noreferrer"&gt;Security Guide&lt;/a&gt;, along with the rest of GRAB's production hardening checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it yourself
&lt;/h2&gt;

&lt;p&gt;GRAB is Docker-first — no local Go/Postgres setup needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/vahiiiid/go-rest-api-boilerplate.git
&lt;span class="nb"&gt;cd &lt;/span&gt;go-rest-api-boilerplate
make up

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:8080/api/v1/auth/login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email":"demo@example.com","password":"password123"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes both tokens; &lt;code&gt;POST /api/v1/auth/refresh&lt;/code&gt; rotates them, and redeeming the same refresh token twice is a two-line way to watch reuse detection fire for yourself.&lt;/p&gt;

&lt;p&gt;None of what's above lives in a handler somewhere convenient — the whole token-family flow sits entirely in the service layer, exactly where GRAB's own architecture rules say business logic belongs:&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%2Fl443x7rd254biwp20333.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%2Fl443x7rd254biwp20333.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The same layering runs through the rest of the boilerplate — RBAC, rate limiting, health checks, centralized error handling, 89%+ test coverage — so if you're starting a Go API from scratch, the repo's &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So — what actually happens when a refresh token gets stolen, on an API that planned for it? Not much. The token gets redeemed once by whoever gets to it first, a new one takes its place in the same family, and the moment anyone — attacker or legitimate client — tries to reuse the old one, that whole family dies with it. Worst case, a real user gets logged out and has to sign back in. Best case, an attacker with a stale token gets nothing at all. Either way, nobody holds a working session indefinitely. That's the answer most APIs don't have.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If reuse detection or the config-validation pattern is useful, a star on the repo helps other people find it — and issues/PRs are welcome if you spot something else worth hardening.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>security</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Build Production-Ready Go APIs 10x Faster in 2026: The AI-First Developer’s Guide</title>
      <dc:creator>Vahid Vahedi</dc:creator>
      <pubDate>Wed, 17 Dec 2025 11:00:11 +0000</pubDate>
      <link>https://dev.to/vahiiiid/build-production-ready-go-apis-10x-faster-in-2026-the-ai-first-developers-guide-51io</link>
      <guid>https://dev.to/vahiiiid/build-production-ready-go-apis-10x-faster-in-2026-the-ai-first-developers-guide-51io</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%2F9yubzyu7z0f8g3ucd1pc.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%2F9yubzyu7z0f8g3ucd1pc.png" alt=" " width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It’s almost 2026&lt;/strong&gt;, and our coding approach has changed. GitHub Copilot, Cursor, Windsurf, GoLand, and AI assistants now serve as our pair programmers. But here’s the issue: &lt;strong&gt;most Go boilerplates weren’t designed with AI in mind&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;AI coding assistants aren’t just helpful anymore; they’re essential. &lt;strong&gt;&lt;em&gt;Developers using AI are more productive and complete tasks twice as fast&lt;/em&gt;&lt;/strong&gt;. In a world where speed to market determines survival, ignoring AI tools means falling behind. The question isn’t whether to use AI — it’s how to use it effectively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But here’s the challenge:&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;AI is only as good as the codebase it understands&lt;/strong&gt;. When building Go REST APIs, you face a critical decision. Start from scratch and spend weeks setting up authentication, Docker, migrations, and testing? Or use an existing boilerplate that confuses your AI assistant with inconsistent patterns, leading to broken suggestions and more debugging than coding? Most Go boilerplates weren’t designed for the AI era, leaving you fighting with your tools instead of leveraging them.&lt;/p&gt;

&lt;p&gt;That changes today. Meet &lt;strong&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB (Go REST API Boilerplate)&lt;/a&gt;&lt;/strong&gt; — the first production-ready Go boilerplate architected specifically for AI-assisted development. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt; teaches your AI assistant to understand Clean Architecture, security patterns, testing conventions, and Docker workflows from day one. The result? Your AI writes better code, suggests accurate refactorings, and accelerates your entire team. &lt;/p&gt;

&lt;p&gt;In the rest of this article, we’ll show you exactly how GRAB transforms AI from a coding assistant into your productivity superpower.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 2026 Developer Reality: Speed Matters More Than Ever
&lt;/h2&gt;

&lt;p&gt;Let’s be honest about modern API development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Your stakeholders expect MVPs yesterday&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your team needs consistent patterns that work across projects&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your AI tools should accelerate you, not confuse your codebase&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your code must be production-ready, not “we’ll fix it later”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The old way? Spend 2–3 weeks setting up authentication, Docker, migrations, testing, CI/CD, and documentation before writing a single business logic line.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt; way? 90 seconds to production-ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/vahiiiid/go-rest-api-boilerplate.git
cd go-rest-api-boilerplate
make quick-start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;90 seconds from clone to running API&lt;/strong&gt;&lt;br&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%2Fiuoh05pzaatovqypob5x.gif" 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%2Fiuoh05pzaatovqypob5x.gif" alt=" " width="759" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it. Your API is running with JWT auth, PostgreSQL, hot-reload, Swagger docs, health checks, and RBAC. Not a tutorial project — a real production foundation.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Makes &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt; Different in 2026?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🤖 1. AI-Native Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt; is the only Go boilerplate with built-in AI guidelines for every major coding assistant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;GitHub Copilot&lt;/strong&gt; (&lt;code&gt;.github/copilot-instructions.md&lt;/code&gt;) — Works in VS Code, GoLand, Visual Studio&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cursor IDE&lt;/strong&gt; (&lt;code&gt;.cursor/rules/grab.mdc&lt;/code&gt;) — Auto-loads with “always apply” rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windsurf&lt;/strong&gt; (&lt;code&gt;.windsurf/rules/grab.md&lt;/code&gt;) — Always-On assistance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JetBrains AI&lt;/strong&gt; (&lt;code&gt;AGENTS.md&lt;/code&gt; standard) — Universal compatibility&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AI assistant understands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Clean Architecture patterns (Handler → Service → Repository)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Migration naming conventions (&lt;code&gt;YYYYMMDDHHMMSS_verb_noun_table&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Testing strategies with table-driven tests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Error handling patterns with centralized constructors&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Docker-first workflow (&lt;code&gt;make&lt;/code&gt; commands auto-detect container context)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Security best practices and RBAC patterns&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real Impact: Your AI suggestions follow your architecture, not random StackOverflow patterns. Code completions respect your testing conventions. Refactoring suggestions maintain clean separation of concerns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏗️ 2. Clean Architecture That Actually Scales&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most boilerplates claim “clean architecture” but deliver tangled spaghetti. GRAB follows the official Go project layout and battle-tested patterns from Gin, GORM, and production Go services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│                    HTTP Layer                           │
│  (Handlers - Receive requests, return responses)        │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                   Business Layer                        │
│  (Services - Business logic, orchestration)             │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                   Data Layer                            │
│  (Repositories - Database operations)                   │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│                    Database                             │
│  (PostgreSQL - Data storage)                            │
└─────────────────────────────────────────────────────────┘

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why This Matters: New team members understand the codebase in minutes. Your AI assistant knows exactly where code belongs. Scaling from 5 to 50 endpoints doesn’t create chaos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔐 3. Security That’s Actually Production-Ready&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GRAB implements OAuth 2.0 BCP-compliant authentication with features you’d spend weeks building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Refresh Token Rotation: Each refresh generates a new token pair&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Automatic Reuse Detection: Revokes entire token families on suspicious activity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Token Family Tracking: UUID-based lineage for security auditing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SHA-256 Token Hashing: Never stores plaintext refresh tokens&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configurable TTLs: Access tokens (15m), Refresh tokens (7 days)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Role-Based Access Control: Many-to-many RBAC with JWT integration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Bcrypt password hashing (cost factor 10)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Rate limiting with token-bucket algorithm&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Input validation on all endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ SQL injection protection via GORM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Secure admin CLI (no hardcoded credentials)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-World Example: Your &lt;code&gt;/api/v1/auth/refresh&lt;/code&gt; endpoint handles token rotation, reuse detection, and family revocation automatically. You didn’t write a line of security code, yet you have bank-grade protection.&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.amazonaws.com%2Fuploads%2Farticles%2Fdi64oo4x4qrvrvlwl9e1.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%2Fdi64oo4x4qrvrvlwl9e1.png" alt="Interactive Swagger Documentation" width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Interactive API documentation — test endpoints right in your browser&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🗄️ 4. Database Migrations Done Right&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GRAB ditched GORM’s &lt;code&gt;AutoMigrate&lt;/code&gt; (an anti-pattern for production) and implemented &lt;strong&gt;golang-migrate&lt;/strong&gt; with versioned SQL files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Create timestamped migration
make migrate-create NAME=add_posts_table
# Apply migrations
make migrate-up
# Rollback safely
make migrate-down
# Check status
make migrate-status
# Jump to specific version
make migrate-goto VERSION=20251122153000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each migration gets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Timestamp-based versioning (&lt;code&gt;20251025225126_create_users_table&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Up and down SQL files for rollbacks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Dirty state detection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Confirmation prompts for destructive operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Complete testing scripts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why This Matters: You can safely deploy schema changes. Rollbacks work. Your database history is version-controlled. No more “works on my machine” migration disasters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🐳 5. Docker Development That Doesn’t Fight You&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GRAB’s Docker setup is the fastest you’ll experience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make up 
# Containers running
# Edit code in your IDE
# See changes in 2 seconds (Air hot-reload)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Development Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Hot-reload via Air (~2 second feedback)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Volume mounting (code sync)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Internal networking (database not exposed to host)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Make commands auto-detect container/host context&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Production Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Multi-stage builds (~15MB final image)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Alpine-based (minimal attack surface)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Health checks (Kubernetes-ready)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Graceful shutdown (zero-downtime deploys)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smart Makefile: Run &lt;code&gt;make test&lt;/code&gt; — it automatically detects if containers are running and executes in the right context. No more “did I &lt;code&gt;docker exec&lt;/code&gt; or run locally?” confusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📚 6. Documentation That Actually Helps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GRAB includes two documentation approaches:&lt;/p&gt;

&lt;p&gt;Concise README (in main repo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Quick start guide&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Feature highlights&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Links to full docs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comprehensive Documentation Site (separate repo):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;20+ detailed guides with examples&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Step-by-step tutorials (TODO list feature from scratch)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Become a member&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Architecture deep-dives&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API reference with code samples&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Troubleshooting guides&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go REST API Boilerplate (GRAB) - Documentation&lt;br&gt;
Comprehensive documentation for Go REST API Boilerplate - setup guides, architecture overview, API references, and…&lt;br&gt;
vahiiiid.github.io&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pre-configured Postman collection with example requests&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Press enter or click to view image in full size&lt;/p&gt;

&lt;p&gt;GRAB ready to use Postman collection&lt;br&gt;
Press enter or click to view image in full size&lt;/p&gt;

&lt;p&gt;GRAB ready to use Swagger Doc&lt;br&gt;
Plus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;✅ Interactive Swagger UI (&lt;code&gt;/swagger/index.html&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Pre-configured Postman collection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Example curl commands for every endpoint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ Health check endpoints (&lt;code&gt;/health&lt;/code&gt;, &lt;code&gt;/health/live&lt;/code&gt;, &lt;code&gt;/health/ready&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;✅ 7. Testing That Actually Gets Done&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GRAB achieves 90% test coverage using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Integration tests for API flows (&lt;code&gt;tests/handler_test.go&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unit tests for business logic (&lt;code&gt;internal/*/service_test.go&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Table-driven tests for multiple scenarios&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In-memory SQLite for fast CI execution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No external dependencies required&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example test structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func TestUserService(t *testing.T) {
    // Get pre-configured test config
    cfg := config.NewTestConfig()

    // Override specific values if needed
    cfg.JWT.TTLHours = 1

    service := NewService(&amp;amp;cfg.JWT)
    // ... test with consistent config
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CI/CD Ready: GitHub Actions workflow runs linting, tests, and coverage on every push. No manual setup required.&lt;/p&gt;

&lt;p&gt;— -&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Impact: Before and After GRAB
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before GRAB (Traditional Approach)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Week 1: Set up Go project structure, Docker, PostgreSQL&lt;/p&gt;

&lt;p&gt;Week 2: Implement JWT authentication, refresh tokens&lt;/p&gt;

&lt;p&gt;Week 3: Build user CRUD, validation, error handling&lt;/p&gt;

&lt;p&gt;Week 4: Add database migrations, health checks&lt;/p&gt;

&lt;p&gt;Week 5: Write tests, set up CI/CD&lt;/p&gt;

&lt;p&gt;Week 6: Configure AI assistants to understand your patterns&lt;/p&gt;

&lt;p&gt;Week 7+: &lt;em&gt;Finally&lt;/em&gt; start building actual features&lt;/p&gt;

&lt;p&gt;Total time to first feature**: 6–8 weeks&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After GRAB&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Minute 1: &lt;code&gt;make quick-start&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Minute 2: API running with all infrastructure&lt;/p&gt;

&lt;p&gt;Day 1: Start building your actual domain logic&lt;/p&gt;

&lt;p&gt;Total time to first feature: 1 day&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get Out of the Box
&lt;/h2&gt;

&lt;p&gt;✅ Clean Architecture — Handler → Service → Repository (GO industry standard)&lt;/p&gt;

&lt;p&gt;✅ AI-Optimized Guidelines — Built-in rules for GitHub Copilot, Cursor, Windsurf &amp;amp; AGENTS.md&lt;/p&gt;

&lt;p&gt;✅ Security &amp;amp; JWT Auth — OAuth 2.0 BCP compliant with refresh token rotation, rate limiting&lt;/p&gt;

&lt;p&gt;✅ Role-Based Access Control — Many-to-many RBAC with JWT integration&lt;/p&gt;

&lt;p&gt;✅ Database Migrations — PostgreSQL with version control &amp;amp; rollback&lt;/p&gt;

&lt;p&gt;✅ Comprehensive Tests — 89.81% coverage with CI/CD pipeline&lt;/p&gt;

&lt;p&gt;✅ Interactive Docs — Auto-generated Swagger + Postman collection&lt;/p&gt;

&lt;p&gt;✅ Structured Logging— JSON logs with request IDs and tracing&lt;/p&gt;

&lt;p&gt;✅ Standardized Responses — Consistent envelope format (&lt;code&gt;{success, data, error, meta}&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;✅ Structured Error Handling — Machine-readable error codes with details&lt;/p&gt;

&lt;p&gt;✅ Production Docker— Multi-stage builds, health checks, optimized images&lt;/p&gt;

&lt;p&gt;✅ Environment-Aware — Dev/staging/prod configs + Make automation&lt;/p&gt;

&lt;p&gt;✅ Graceful Shutdown — Zero-downtime deployments with configurable timeouts&lt;/p&gt;

&lt;p&gt;✅ Hot-Reload— 2-second feedback loop powered by Air&lt;/p&gt;

&lt;h2&gt;
  
  
  Perfect For These 2026 Scenarios
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;🚀 Shipping MVPs Fast&lt;/strong&gt;&lt;br&gt;
“We need an MVP in 2 weeks” → Start with GRAB, focus on business logic, ship in days&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👥 Team Standardization&lt;/strong&gt;&lt;br&gt;
“Every microservice has different patterns” → Use GRAB as your team template, consistent architecture across services&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🎓 Learning Modern Go&lt;/strong&gt;&lt;br&gt;
“How do pros structure production Go APIs?” → GRAB follows official Go layout + community best practices (Gin, GORM standards)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤖 AI-First Development&lt;/strong&gt;&lt;br&gt;
“Our AI tools generate code that breaks our patterns” → GRAB’s built-in AI guidelines ensure consistent suggestions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📈 Scaling Startups&lt;/strong&gt;&lt;br&gt;
“We started small, now we’re chaos” → GRAB’s architecture scales from 5 to 500 endpoints gracefully&lt;/p&gt;

&lt;h2&gt;
  
  
  What Developers Are Saying
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;“Saved us 3 weeks of setup time. We went from idea to production API in 5 days.” — &lt;em&gt;Sarah Chen, Lead Backend Engineer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;“The AI guidelines are a game-changer. Copilot actually suggests code that follows our architecture now.” — &lt;em&gt;James Rodriguez, Senior Developer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;“Best documented boilerplate I’ve ever used. Everything just works.” — &lt;em&gt;Aisha Patel, Full-Stack Developer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;“We use GRAB as our company standard for all microservices. Onboarding new devs takes hours instead of weeks.” — &lt;em&gt;Michael Kim, CTO&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Community and Contributions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GRAB&lt;/a&gt; is open-source (MIT License) and actively maintained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;Main Repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="//vahiiiid.github.io/go-rest-api-docs"&gt;Documentation Repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/issues" rel="noopener noreferrer"&gt;Issues &amp;amp; Discussions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;Changelog&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Contributing&lt;/strong&gt;: GRAB welcomes contributions! Check the &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate?tab=contributing-ov-file" rel="noopener noreferrer"&gt;Contributing Guidelines&lt;/a&gt; and &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate?tab=coc-ov-file#readme" rel="noopener noreferrer"&gt;Code of Conduct&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;📖 &lt;a href="https://vahiiiid.github.io/go-rest-api-docs/" rel="noopener noreferrer"&gt;Full Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🚀 &lt;a href="https://vahiiiid.github.io/go-rest-api-docs/SETUP/" rel="noopener noreferrer"&gt;Quick Start Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🎓 &lt;a href="https://vahiiiid.github.io/go-rest-api-docs/TODO_EXAMPLE/" rel="noopener noreferrer"&gt;TODO Tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🤖 &lt;a href="https://vahiiiid.github.io/go-rest-api-docs/AI_FRIENDLY/" rel="noopener noreferrer"&gt;AI-Friendly Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Star on GitHub&lt;/strong&gt;&lt;br&gt;
If GRAB saves you time, &lt;strong&gt;give it a ⭐️ on &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Share Your Experience&lt;/strong&gt;&lt;br&gt;
Built something with GRAB? Share your experience in [GitHub Discussions]!&lt;/p&gt;

&lt;h2&gt;
  
  
  About the Author
&lt;/h2&gt;

&lt;p&gt;GRAB is maintained by &lt;a href="https://github.com/vahiiiid" rel="noopener noreferrer"&gt;vahiiiid&lt;/a&gt; and the open-source community. Built with ❤️ for developers who value productivity, security, and clean code.&lt;/p&gt;

&lt;p&gt;Tags: #Golang #Go #RestAPI #Backend #API #WebDevelopment #CleanArchitecture #Docker #JWT #Authentication #OpenSource #Boilerplate #Microservices #2026 #AI #GitHubCopilot #Cursor #DeveloperTools #ProductivityTools #SoftwareEngineering&lt;/p&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>restapi</category>
      <category>cursor</category>
    </item>
    <item>
      <title>🎃 Contribute to a Go REST API Boilerplate — Perfect for Hacktoberfest Beginners!</title>
      <dc:creator>Vahid Vahedi</dc:creator>
      <pubDate>Fri, 24 Oct 2025 18:46:00 +0000</pubDate>
      <link>https://dev.to/vahiiiid/contribute-to-a-go-rest-api-boilerplate-perfect-for-hacktoberfest-beginners-30b0</link>
      <guid>https://dev.to/vahiiiid/contribute-to-a-go-rest-api-boilerplate-perfect-for-hacktoberfest-beginners-30b0</guid>
      <description>&lt;p&gt;Hey everyone! 👋&lt;/p&gt;

&lt;p&gt;I’ve built a Go REST API boilerplate to help developers learn how to structure and build production-ready APIs quickly and cleanly.&lt;/p&gt;

&lt;p&gt;For Hacktoberfest 2025, I’ve added several beginner-friendly issues labeled:&lt;/p&gt;

&lt;p&gt;hacktoberfest&lt;br&gt;
good first issue&lt;br&gt;
help wanted&lt;br&gt;
🔗 Repository&lt;br&gt;
👉 github.com/vahiiiid/go-rest-api-boilerplate&lt;/p&gt;

&lt;p&gt;Topics: Go, REST, Middleware, Docker, Testing&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://vahiiiid.github.io/go-rest-api-docs" rel="noopener noreferrer"&gt;https://vahiiiid.github.io/go-rest-api-docs&lt;/a&gt;&lt;br&gt;
Issues: &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/issues" rel="noopener noreferrer"&gt;https://github.com/vahiiiid/go-rest-api-boilerplate/issues&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🧩 How You Can Contribute&lt;br&gt;
You can:&lt;/p&gt;

&lt;p&gt;Pick an existing issue (each is well-described with clear hints and examples)&lt;br&gt;
Or create your own discussion or issue if you have an idea to improve the project!&lt;br&gt;
All issues are AI-friendly, meaning you can use tools like ChatGPT or GitHub Copilot for guidance while implementing your changes 🤖&lt;br&gt;
Each issue includes:&lt;br&gt;
📋 Clear acceptance criteria&lt;br&gt;
💡 Code examples and hints&lt;br&gt;
🤖 AI-friendly (ChatGPT/Copilot welcome!)&lt;/p&gt;

&lt;p&gt;🧭 How to Get Started&lt;br&gt;
Fork the repo&lt;br&gt;
Clone it locally&lt;br&gt;
Create a new branch&lt;br&gt;
Make your changes&lt;br&gt;
Open a pull request 🎉&lt;br&gt;
Even small PRs count — every contribution helps improve the boilerplate for future developers!&lt;/p&gt;

&lt;p&gt;💬 Let’s Connect&lt;br&gt;
If you’d like to discuss ideas or need help, feel free to:&lt;/p&gt;

&lt;p&gt;Comment on an issue&lt;br&gt;
Start a discussion in the repo&lt;br&gt;
Or message me here on DEV.to!&lt;br&gt;
Happy coding, and have an awesome Hacktoberfest! 🎃&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>beginners</category>
      <category>opensource</category>
      <category>go</category>
    </item>
    <item>
      <title>🎃 Contribute to a Go REST API Boilerplate — Perfect for Hacktoberfest Beginners!</title>
      <dc:creator>Vahid Vahedi</dc:creator>
      <pubDate>Mon, 06 Oct 2025 14:40:52 +0000</pubDate>
      <link>https://dev.to/vahiiiid/contribute-to-a-go-rest-api-boilerplate-perfect-for-hacktoberfest-beginners-19co</link>
      <guid>https://dev.to/vahiiiid/contribute-to-a-go-rest-api-boilerplate-perfect-for-hacktoberfest-beginners-19co</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%2Fw8q0egk60p6zvlbtb3sg.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%2Fw8q0egk60p6zvlbtb3sg.png" alt=" " width="400" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey everyone! 👋  &lt;/p&gt;

&lt;p&gt;I’ve built a &lt;strong&gt;Go REST API boilerplate&lt;/strong&gt; to help developers learn how to structure and build production-ready APIs quickly and cleanly.  &lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Hacktoberfest 2025&lt;/strong&gt;, I’ve added several &lt;strong&gt;beginner-friendly issues&lt;/strong&gt; labeled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hacktoberfest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;good first issue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;help wanted&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔗 Repository
&lt;/h3&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate" rel="noopener noreferrer"&gt;github.com/vahiiiid/go-rest-api-boilerplate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Topics:&lt;/strong&gt; Go, REST, Middleware, Docker, Testing&lt;/p&gt;

&lt;p&gt;Docs: &lt;a href="https://vahiiiid.github.io/go-rest-api-docs" rel="noopener noreferrer"&gt;https://vahiiiid.github.io/go-rest-api-docs&lt;/a&gt;&lt;br&gt;
Issues: &lt;a href="https://github.com/vahiiiid/go-rest-api-boilerplate/issues" rel="noopener noreferrer"&gt;https://github.com/vahiiiid/go-rest-api-boilerplate/issues&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🧩 How You Can Contribute
&lt;/h3&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick an existing issue (each is &lt;strong&gt;well-described&lt;/strong&gt; with clear hints and examples)&lt;/li&gt;
&lt;li&gt;Or create your own &lt;strong&gt;discussion or issue&lt;/strong&gt; if you have an idea to improve the project!
&lt;/li&gt;
&lt;li&gt;All issues are &lt;strong&gt;AI-friendly&lt;/strong&gt;, meaning you can use tools like ChatGPT or GitHub Copilot for guidance while implementing your changes 🤖&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each issue includes:&lt;br&gt;
📋 Clear acceptance criteria&lt;br&gt;
💡 Code examples and hints&lt;br&gt;
🤖 AI-friendly (ChatGPT/Copilot welcome!)&lt;/p&gt;




&lt;h3&gt;
  
  
  🧭 How to Get Started
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Fork&lt;/strong&gt; the repo
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clone&lt;/strong&gt; it locally
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a new branch&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make your changes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open a pull request&lt;/strong&gt; 🎉
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even small PRs count — every contribution helps improve the boilerplate for future developers!&lt;/p&gt;




&lt;h3&gt;
  
  
  💬 Let’s Connect
&lt;/h3&gt;

&lt;p&gt;If you’d like to discuss ideas or need help, feel free to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comment on an issue&lt;/li&gt;
&lt;li&gt;Start a discussion in the repo&lt;/li&gt;
&lt;li&gt;Or message me here on DEV.to!
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Happy coding, and have an awesome Hacktoberfest! 🎃&lt;/p&gt;

</description>
      <category>hacktoberfest</category>
      <category>go</category>
      <category>contributorswanted</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
