<?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>Forem: Mahmud Hasan Rafid</title>
    <description>The latest articles on Forem by Mahmud Hasan Rafid (@princerafid01).</description>
    <link>https://forem.com/princerafid01</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F968502%2F171c623b-00a5-407d-b3a9-07449193e0c8.jpeg</url>
      <title>Forem: Mahmud Hasan Rafid</title>
      <link>https://forem.com/princerafid01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/princerafid01"/>
    <language>en</language>
    <item>
      <title>Building a Domain-Driven Go REST API (And Why It Scales Better Than Flat Structure)</title>
      <dc:creator>Mahmud Hasan Rafid</dc:creator>
      <pubDate>Tue, 31 Mar 2026 11:33:59 +0000</pubDate>
      <link>https://forem.com/princerafid01/building-a-domain-driven-go-rest-api-and-why-it-scales-better-than-flat-structure-1g6c</link>
      <guid>https://forem.com/princerafid01/building-a-domain-driven-go-rest-api-and-why-it-scales-better-than-flat-structure-1g6c</guid>
      <description>&lt;p&gt;Most Go backend projects start the same way. A &lt;code&gt;main.go&lt;/code&gt;, a router, a database connection, and then a slow drift into a structure that technically works but becomes painful to extend after the third or fourth feature. I've been there enough times that I finally sat down, extracted the architecture from a production project I was happy with, and published it as an open-source boilerplate.&lt;/p&gt;

&lt;p&gt;This post explains the architecture, why Domain-Driven Design (DDD) fits Go surprisingly well, and how the boilerplate is structured so you can hit the ground running on your next project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/princerafid01/go-rest-boilerplate" rel="noopener noreferrer"&gt;https://github.com/princerafid01/go-rest-boilerplate&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why DDD in Go?
&lt;/h2&gt;

&lt;p&gt;Domain-Driven Design gets a bad reputation for being a Java/C# thing — verbose, ceremonial, and overkill for most projects. That reputation is mostly deserved for the full tactical DDD toolkit (aggregates, value objects, domain events). But the &lt;em&gt;core idea&lt;/em&gt; of DDD is simple and language-agnostic:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The domain model is the center of your application. Everything else — HTTP, SQL, auth — is infrastructure that serves the domain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go is actually a great fit for this style because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interfaces are implicit and lightweight, so defining boundaries between layers costs almost nothing&lt;/li&gt;
&lt;li&gt;The package system naturally enforces separation — packages can't have circular imports&lt;/li&gt;
&lt;li&gt;Go's preference for small, focused types maps cleanly to domain entities and service objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a codebase where your business logic is completely isolated from delivery mechanisms (HTTP, gRPC) and persistence (Postgres, any other DB). You can swap either without touching your domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture at a Glance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
    │
    ▼
Middleware Chain (Preflight → CORS → Logger)
    │
    ▼
Route-Specific Middleware (JWT Auth → injects userID into context)
    │
    ▼
Handler  ← parses HTTP request, calls service
    │
    ▼
Service  ← business rules, depends only on interfaces (the domain layer)
    │
    ▼
Repo     ← SQL implementation, satisfies the repo interface
    │
    ▼
PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key rule:&lt;/strong&gt; each layer only depends on the layer below it &lt;em&gt;through an interface&lt;/em&gt;. The only file that imports concrete implementations across layers is &lt;code&gt;cmd/serve.go&lt;/code&gt; — the application wiring root. Everything else is interface-to-interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── main.go
├── cmd/serve.go                  # Application layer — wires all dependencies
├── config/config.go              # Env-based config
├── domain/                       # Pure domain entities — no DB, no HTTP
│   ├── user.go
│   └── product.go
├── {feature}/                    # Bounded context per domain feature
│   ├── port.go                   # Service interface + Repo interface
│   └── service.go                # Business logic implementation
├── repo/                         # Infrastructure — sqlx SQL implementations
│   └── {feature}.go
├── rest/                         # Delivery mechanism — HTTP
│   ├── server.go
│   ├── middlewares/
│   └── handlers/{feature}/
│       ├── port.go               # What the handler needs from the service
│       ├── handler.go
│       ├── dto.go                # Request/response types
│       ├── routes.go
│       └── *.go                  # One file per action
├── utils/                        # Shared helpers (JWT, responses, pagination)
└── migrations/                   # Up/down SQL pairs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through what each layer actually does.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Domain Layer
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;domain/&lt;/code&gt; package is the heart of the application. It contains plain Go structs that represent your business entities — nothing else.&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;// domain/user.go&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;           &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="n"&gt;Email&lt;/span&gt;        &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;PasswordHash&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;         &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;CreatedAt&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;Time&lt;/span&gt;
    &lt;span class="n"&gt;UpdatedAt&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;Time&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No HTTP types. No database tags that bleed framework concerns into your model. No third-party imports. If you need to change your database driver tomorrow, this file doesn't change. That's the point.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Feature (Bounded Context) Layer
&lt;/h2&gt;

&lt;p&gt;Each domain feature gets its own package. For example, a &lt;code&gt;product&lt;/code&gt; feature would have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;product/port.go&lt;/code&gt;&lt;/strong&gt; — defines the contracts this feature exposes and depends on:&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;// What this feature exposes to its consumers&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;productHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="c"&gt;// embeds the handler's Service interface&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// What this feature needs from the data layer&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ProductRepo&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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="n"&gt;Get&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="n"&gt;userID&lt;/span&gt; &lt;span class="kt"&gt;int64&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="kt"&gt;int64&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userID&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&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="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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="n"&gt;Delete&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="n"&gt;userID&lt;/span&gt; &lt;span class="kt"&gt;int64&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service interface embeds &lt;code&gt;productHandler.Service&lt;/code&gt; — this is a deliberate design decision. The handler defines what it needs from the service layer, and the feature-level Service interface must satisfy that contract. If you add a method to the handler's interface, the Go compiler immediately tells you to implement it everywhere that needs to. No runtime surprises.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;product/service.go&lt;/code&gt;&lt;/strong&gt; — implements business logic:&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;type&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="n"&gt;ProductRepo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt; &lt;span class="n"&gt;ProductRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&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;repo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&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;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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="c"&gt;// Business rules go here before delegating to repo&lt;/span&gt;
    &lt;span class="k"&gt;return&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;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;The service only knows about domain types and its repo interface. It has no idea whether it's being called from an HTTP handler, a CLI command, or a test.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Infrastructure Layer (Repo)
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;repo/&lt;/code&gt; package is pure infrastructure. It satisfies the repo interfaces defined in the feature layer using sqlx and raw SQL:&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;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;productRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;`
        INSERT INTO products (user_id, name, price)
        VALUES (:user_id, :name, :price)
        RETURNING id, created_at, updated_at
    `&lt;/span&gt;
    &lt;span class="n"&gt;rows&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;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamedQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&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;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;err&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;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UpdatedAt&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&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;No ORM, no magic. Just SQL. The repo knows about the database, but the domain and service layers don't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Delivery Layer (HTTP Handlers)
&lt;/h2&gt;

&lt;p&gt;Handlers are thin. Their only job is to parse the HTTP request, call the service, and write the HTTP response. They don't contain business logic.&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;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&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;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&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;Context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UserIDKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&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;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Unauthorized"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="n"&gt;CreateRequest&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;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&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;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;req&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="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Invalid request body"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;result&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;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;svc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&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;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Internal server error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&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;Each handler action gets its own file (&lt;code&gt;create.go&lt;/code&gt;, &lt;code&gt;get.go&lt;/code&gt;, &lt;code&gt;list.go&lt;/code&gt;, etc.). This keeps files small and makes navigation obvious.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Middleware System
&lt;/h2&gt;

&lt;p&gt;The boilerplate uses a small custom &lt;code&gt;Manager&lt;/code&gt; with two levels of middleware:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global&lt;/strong&gt; — applied to every request, registered in &lt;code&gt;server.go&lt;/code&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="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Preflight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Per-route&lt;/strong&gt; — applied to specific routes using &lt;code&gt;manager.With(...)&lt;/code&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="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /api/products"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;middlewares&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticateJWT&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;The JWT middleware validates the HS256 signature, decodes the payload, and injects the &lt;code&gt;userID&lt;/code&gt; into the request context using a typed context key — avoiding the string key collision problem that plagues many Go middleware implementations.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Application Layer: cmd/serve.go
&lt;/h2&gt;

&lt;p&gt;This is where all the wiring happens. It's the only place in the codebase where concrete types meet:&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;Serve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cnf&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;GetConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;dbCon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MigrateDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbCon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./migrations"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Infrastructure&lt;/span&gt;
    &lt;span class="n"&gt;productRepo&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewProductRepo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbCon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Domain services&lt;/span&gt;
    &lt;span class="n"&gt;productSvc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Delivery&lt;/span&gt;
    &lt;span class="n"&gt;middlewares&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;middleware&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewMiddlewares&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prodHandler&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;productHandler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;middlewares&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;productSvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Server&lt;/span&gt;
    &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prodHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&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;Everything flows top-down. Dependencies are explicit. There's no magic injection framework — just function calls. If something breaks, you can trace it by reading this file alone.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding a New Feature in 7 Steps
&lt;/h2&gt;

&lt;p&gt;This is where the architecture pays for itself. To add a new &lt;code&gt;Order&lt;/code&gt; resource:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;domain/order.go&lt;/code&gt;&lt;/strong&gt; — the entity struct&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;rest/handlers/order/port.go&lt;/code&gt;&lt;/strong&gt; — the Service interface the handler needs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;order/port.go&lt;/code&gt;&lt;/strong&gt; — embeds the handler's Service interface, defines OrderRepo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;order/service.go&lt;/code&gt;&lt;/strong&gt; — implements the business logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;repo/order.go&lt;/code&gt;&lt;/strong&gt; — implements OrderRepo with SQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;rest/handlers/order/&lt;/code&gt;&lt;/strong&gt; — handler, dto, routes, one file per action&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire it in &lt;code&gt;cmd/serve.go&lt;/code&gt;&lt;/strong&gt; — create repo → service → handler → register routes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every step is additive. You never modify existing files to add a new feature. That's the DDD principle of bounded contexts in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;net/http&lt;/code&gt; (stdlib)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sqlx&lt;/code&gt; + &lt;code&gt;lib/pq&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migrations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rubenv/sql-migrate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Hand-rolled HS256 JWT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;godotenv&lt;/code&gt; + env vars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passwords&lt;/td&gt;
&lt;td&gt;&lt;code&gt;golang.org/x/crypto/bcrypt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No heavy framework. The stdlib HTTP mux has supported path parameters since Go 1.22, which covers the routing needs of most REST APIs without pulling in gorilla/mux or chi.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



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

&lt;span class="c"&gt;# Rename the module&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.go"&lt;/span&gt; | xargs &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/boilerplate/my-project/g'&lt;/span&gt;
go mod edit &lt;span class="nt"&gt;-module&lt;/span&gt; my-project

&lt;span class="c"&gt;# Configure environment&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
&lt;span class="c"&gt;# Edit .env with your DB credentials and JWT secret&lt;/span&gt;

go mod tidy
go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrations run automatically on startup. The &lt;code&gt;example&lt;/code&gt; feature included in the repo gives you a working reference for the full CRUD pattern while you build out your own features.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;DDD in Go doesn't have to mean heavyweight ceremony. At its core it's just one rule: &lt;strong&gt;keep your domain clean, and treat everything else as infrastructure&lt;/strong&gt;. Go's interface system and package model make this remarkably natural to implement.&lt;/p&gt;

&lt;p&gt;If you've been looking for a Go backend structure that scales past the first few features without becoming a mess — give this a try. And if you have ideas for improvements, PRs are very welcome.&lt;/p&gt;

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

</description>
      <category>go</category>
      <category>webdev</category>
      <category>restapi</category>
    </item>
    <item>
      <title>The Shopify Resource Picker API: A Developer's Journey Through Version Changes (August 2025 )</title>
      <dc:creator>Mahmud Hasan Rafid</dc:creator>
      <pubDate>Sat, 16 Aug 2025 03:22:17 +0000</pubDate>
      <link>https://forem.com/princerafid01/the-shopify-resource-picker-api-a-developers-journey-through-version-changes-august-2025--52ne</link>
      <guid>https://forem.com/princerafid01/the-shopify-resource-picker-api-a-developers-journey-through-version-changes-august-2025--52ne</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As a developer working with Shopify's ecosystem, I recently encountered a familiar yet frustrating challenge: implementing the resource picker API. What should have been a straightforward integration turned into a lesson about the limitations of AI powered development tools when dealing with rapidly evolving APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;I needed to implement a product picker in my Shopify app that would allow users to select multiple products from their store. The concept seemed simple enough - use Shopify's resource picker API to open a modal where users can search and select products.&lt;/p&gt;

&lt;p&gt;However, the implementation proved more challenging than expected, primarily due to the frequent changes in Shopify's API versions and documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;After several attempts and much trial and error, I finally got the resource picker working. Here's the working implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAppBridge&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@shopify/app-bridge-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductResourcePicker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAppBridge&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openProductPicker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productPicker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resourcePicker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;multiple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;initialQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;macbook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;showHidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productPicker&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;productPicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;productPicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Selection made:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productPicker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selection&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;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;openProductPicker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Open Product Picker&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;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;The key components that made this work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;useAppBridge()&lt;/code&gt;&lt;/strong&gt;: Properly initializing the Shopify app bridge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;app.resourcePicker()&lt;/code&gt;&lt;/strong&gt;: Using the correct method call&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proper configuration&lt;/strong&gt;: Setting the right parameters for product selection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The AI Frustration
&lt;/h2&gt;

&lt;p&gt;Here's where things got interesting - and frustrating. I initially turned to AI coding assistants for help, but they consistently provided outdated or incorrect information. &lt;/p&gt;

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

&lt;p&gt;While AI development tools are incredibly powerful and can significantly speed up development, they have limitations when dealing with rapidly evolving APIs like Shopify's. &lt;/p&gt;

&lt;p&gt;My experience with the Shopify resource picker API taught me that sometimes the "old-fashioned" approach of reading documentation, testing incrementally, and learning from the community is still the most reliable path to success.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a Scalable Job Queue System with AWS and Laravel</title>
      <dc:creator>Mahmud Hasan Rafid</dc:creator>
      <pubDate>Sat, 09 Nov 2024 06:35:52 +0000</pubDate>
      <link>https://forem.com/princerafid01/building-a-scalable-job-queue-system-with-aws-and-laravel-1jeo</link>
      <guid>https://forem.com/princerafid01/building-a-scalable-job-queue-system-with-aws-and-laravel-1jeo</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;br&gt;
Ever wondered how large applications handle thousands of background tasks efficiently? Today, I’ll guide you through a real-world solution our &lt;a href="https://storagely.io" rel="noopener noreferrer"&gt;storagely.io&lt;/a&gt; team developed, which integrates AWS services with Laravel to build a powerful job queue system. This system handles everything from scheduled syncs to on-demand processing, all while maintaining reliability and scalability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;&lt;br&gt;
Imagine you're running a service that needs to sync data for multiple clients regularly. Some syncs need to happen automatically every few hours, while others should be triggered manually by users. You also need to ensure jobs don't duplicate, processes are monitored, and everything is logged properly. Sounds complex? Let's break it down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Solution: A Three-Tier Queue Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1: Automated Sync System&lt;/strong&gt;&lt;br&gt;
Think of this as your reliable alarm clock. Using AWS EventBridge (the alarm clock) and Lambda (the person who wakes up), the system automatically checks for work that needs to be done. Every 15 minutes, EventBridge triggers a Lambda function that asks: "Hey, do any clients need their data synced?"&lt;br&gt;
Here's what happens:&lt;/p&gt;

&lt;p&gt;EventBridge says "Time to check for syncs!"&lt;br&gt;
Lambda wakes up and says "Let me check the list of clients"&lt;br&gt;
For each client, Lambda asks "Does this client already have a sync in progress?"&lt;br&gt;
If not, Lambda says "Ok, let's create a new sync job for this client"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2: On-Demand Processing&lt;/strong&gt;&lt;br&gt;
Sometimes users can't wait for the automated schedule. Maybe they've made important changes and need data synced right now. That's where our on-demand system comes in. It's like having a "Sync Now" button that users can press whenever they need it.&lt;br&gt;
When a user hits that button:&lt;/p&gt;

&lt;p&gt;The system creates a high-priority job&lt;br&gt;
This job jumps into a "speedy queue"&lt;br&gt;
It gets processed faster than regular scheduled jobs&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3: The Job Processor&lt;/strong&gt;&lt;br&gt;
This is where the actual work happens. Running on AWS EC2, this system is like a well-organized factory floor. It uses a tool called Supervisor (think of it as a factory manager) to ensure workers (processes) are always available to handle jobs.&lt;br&gt;
The process flows like this:&lt;/p&gt;

&lt;p&gt;Jobs arrive from either automated or on-demand sources&lt;br&gt;
Workers pick up these jobs&lt;br&gt;
Each job gets processed&lt;br&gt;
Results get stored in a MySQL database&lt;br&gt;
Everything gets logged in CloudWatch&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Magic Behind The Scenes&lt;/strong&gt;&lt;br&gt;
Let's peek at how the automated part works. The Lambda function, written in Node.js, is surprisingly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;javascriptCopyexports.handler = async (event) =&amp;gt; {
    // Check for existing jobs
    const clients = await fetchClientList();

    for (const client of clients) {
        // Don't create duplicate jobs
        if (!(await hasExistingJob(client))) {
            // Create new sync job
            await createSyncJob(client);
        }
    }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitoring and Reliability&lt;/strong&gt;&lt;br&gt;
Every good system needs monitoring. Using CloudWatch, we track everything:&lt;/p&gt;

&lt;p&gt;How many jobs are running?&lt;br&gt;
Are any jobs failing?&lt;br&gt;
How long do jobs take to complete?&lt;br&gt;
Are there any bottlenecks?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
Building a queue system might seem daunting at first, but by breaking it down into manageable pieces and using the right tools, we've created a robust solution that handles both automated and on-demand tasks efficiently. The combination of AWS services (EventBridge, Lambda, EC2) with Laravel's queue system provides a perfect balance of reliability, scalability, and maintainability.&lt;br&gt;
Whether you're handling data syncs, processing reports, or managing any other background tasks, this architecture provides a solid foundation that can grow with your needs. The best part? It runs like clockwork, requiring minimal maintenance while providing maximum visibility into its operations.&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%2F1tkx7z5vli2gc2j4zzot.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%2F1tkx7z5vli2gc2j4zzot.png" alt="aws lambda laravel queue image " width="800" height="935"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>queue</category>
      <category>lambda</category>
      <category>eventbridge</category>
    </item>
    <item>
      <title>How Git Cherry-Pick Saved My Day: A Tale of Branch Chaos 🍒</title>
      <dc:creator>Mahmud Hasan Rafid</dc:creator>
      <pubDate>Mon, 14 Oct 2024 05:26:58 +0000</pubDate>
      <link>https://forem.com/princerafid01/how-git-cherry-pick-saved-my-day-a-tale-of-branch-chaos-5e4</link>
      <guid>https://forem.com/princerafid01/how-git-cherry-pick-saved-my-day-a-tale-of-branch-chaos-5e4</guid>
      <description>&lt;p&gt;Ever had one of those moments where a single command turns disaster into triumph? That was me today, through a maze of Git branches.&lt;/p&gt;

&lt;p&gt;The scene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Three branches: main (production), test (staging), and feature&lt;/li&gt;
&lt;li&gt;Main branch lagging behind the test&lt;/li&gt;
&lt;li&gt;Feature branch spawned from an outdated main&lt;/li&gt;
&lt;li&gt;PRs flowing from feature to test&lt;/li&gt;
&lt;li&gt;Conflict resolution on GitHub’s web interface (pro tip: avoid this if possible!)&lt;/li&gt;
&lt;li&gt;Post-conflict feature branch: a mix of my work and others, looking like the test branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge: My feature branch now had commits from the test branch, making it impossible to merge directly into the production (main) branch.&lt;/p&gt;

&lt;p&gt;Enter git cherry-pick, the unsung hero of version control. Here’s what I did:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identified my commits in the messy feature branch&lt;/li&gt;
&lt;li&gt;Created a new branch from the updated main&lt;/li&gt;
&lt;li&gt;Cherry-picked my commits from the old feature branch into the new one&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result? I got a new updated feature branch branching from the production branch with only my changes, merge crisis averted, and valuable lessons learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Keep branches updated regularly&lt;/li&gt;
&lt;li&gt;Resolve conflicts locally when possible&lt;/li&gt;
&lt;li&gt;Make small, focused commits&lt;/li&gt;
&lt;li&gt;Master cherry-picking for precise code management&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember folks: In the orchard of code, sometimes you need to carefully pick your cherries instead of shaking the whole tree. 🌳🍒&lt;/p&gt;

</description>
      <category>github</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
