{
  "slug": "Building-a-Clean--Secure--and-Scalable-Go-Web-API--A-Layered-Architecture-Guide-ee3afe4aa58d",
  "title": "Building a Clean, Secure, and Scalable Go Web API: A Layered Architecture Guide",
  "subtitle": "In the previous post, a Swagger file was created to provide clearer documentation. At this stage, the focus is on designing a product…",
  "excerpt": "In the previous post, a Swagger file was created to provide clearer documentation. At this stage, the focus is on designing a product…",
  "date": "2025-08-23",
  "tags": [
    "Go",
    "Architecture",
    "API"
  ],
  "readingTime": "4 min",
  "url": "https://medium.com/@mobinshaterian/building-a-clean-secure-and-scalable-go-web-api-a-layered-architecture-guide-ee3afe4aa58d",
  "hero": "https://cdn-images-1.medium.com/max/800/1*EyoadzC84Fozo1FBp1saVg.jpeg",
  "content": [
    {
      "type": "paragraph",
      "html": "In the previous post, a Swagger file was created to provide clearer documentation. At this stage, the focus is on designing a product structure to outline the project. The main objective is to implement CRUD functionality in the admin panel and define the required Golang structures."
    },
    {
      "type": "paragraph",
      "html": "In this article, we’ll walk through a real-world example of how to structure a Go web application using layered architecture, and solve a common but critical problem: showing different data to different users — like Admins vs. Clients — without compromising security or maintainability."
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*EyoadzC84Fozo1FBp1saVg.jpeg",
      "alt": "Building a Clean, Secure, and Scalable Go Web API: A Layered Architecture Guide",
      "caption": "",
      "width": 1120,
      "height": 1120
    },
    {
      "type": "heading",
      "level": 2,
      "text": "🎯 The Problem"
    },
    {
      "type": "paragraph",
      "html": "Imagine you’re building an e-commerce backend with two types of users:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "<strong>Admins</strong>: Need full access to product details (price, cost, inventory, creation date).",
        "<strong>Clients</strong>: Should only see public info (name, ID, selling price)."
      ]
    },
    {
      "type": "paragraph",
      "html": "<strong>How do you:</strong>"
    },
    {
      "type": "list",
      "ordered": true,
      "items": [
        "Structure your code cleanly?",
        "Prevent sensitive data from leaking to clients?",
        "Avoid code duplication and tight coupling?"
      ]
    },
    {
      "type": "paragraph",
      "html": "Let’s explore the journey from a basic handler-based app to a clean, layered, and secure Go API."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "🏗️ Step 1: Start with Layered Architecture"
    },
    {
      "type": "paragraph",
      "html": "A clean Go project should separate concerns into distinct layers:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "internal/├── model/       → Data structures (e.g., Product)├── repository/  → Data access (DB,\nin-memory store)├── service/     → Business logic├── controller/  → HTTP handlers└── dto/         →\nData Transfer Objects (for API responses)"
    },
    {
      "type": "paragraph",
      "html": "This follows the Hexagonal or Clean Architecture principles, where:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Lower layers don’t depend on higher ones.",
        "The domain model (<code>Product</code>) is the single source of truth."
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "❌ Common Mistake: Defining Product in Multiple Layers"
    },
    {
      "type": "paragraph",
      "html": "A tempting but dangerous shortcut is to define <code>Product</code> separately in <code>service</code> and <code>repository</code>:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "// ❌ In service/product.gotype Product struct {    ID    string    Name  string    Price float64}//\n❌ In repository/product.gotype Product struct {    ID    string    Name  string    Price float64}"
    },
    {
      "type": "paragraph",
      "html": "Even if they look identical, Go treats them as completely different types."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "💥 Consequences:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "You can’t pass <code>repository.Product</code> to <code>service.Product</code> directly.",
        "Manual mapping is required → boilerplate code.",
        "If you add a field (e.g., <code>Category</code>), you must update both structs.",
        "Risk of inconsistency and bugs.",
        "Tight coupling between layers."
      ]
    },
    {
      "type": "paragraph",
      "html": "This violates the DRY principle and makes your app fragile."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "✅ Solution: One Product in model/"
    },
    {
      "type": "paragraph",
      "html": "Define <code>Product</code> once, in <code>internal/model/product.go</code>:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "package modeltype Product struct {\n    ID string\n    `json:\"id\"`\n    Name string\n    `json:\"name\"`\n    Price float64 `json:\"price\"`\n    CostPrice float64 `json:\"-\"` // Internal field\n    Inventory int `json:\"-\"` // Hidden by default\n    CreatedAt time.Time `json:\"created_at\"`\n}"
    },
    {
      "type": "paragraph",
      "html": "Now, all layers are imported and used <code>model.Product</code>:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "import \"your-module/internal/model\"func (s *ProductService) GetProduct(id string) (model.Product,\nerror) {\n    ...\n}"
    },
    {
      "type": "paragraph",
      "html": "✅ No duplication<br>✅ No mapping<br>✅ One change updates all layers"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "🔐 Step 2: Control What Data Is Exposed"
    },
    {
      "type": "paragraph",
      "html": "Now comes the real challenge: admins see everything, but clients see only public fields."
    },
    {
      "type": "paragraph",
      "html": "You might be tempted to use JSON tags like <code>json:\"-\"</code> or conditionally omit fields — but that’s error-prone and hard to test."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "✅ Best Practice: Use DTOs (Data Transfer Objects)"
    },
    {
      "type": "paragraph",
      "html": "Create response-specific structs in <code>internal/dto/product_dto.go</code>:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "package dtotype ClientProductResponse struct {\n    ID string `json:\"id\"`\n    Name string `json:\"name\"`\n    Price float64 `json:\"price\"`\n}\ntype AdminProductResponse struct {\n    ID string\n    `json:\"id\"`\n    Name string\n    `json:\"name\"`\n    Price float64 `json:\"price\"`\n    CostPrice float64 `json:\"cost_price\"`\n    Inventory int `json:\"inventory\"`\n    CreatedAt time.Time `json:\"created_at\"`\n}"
    },
    {
      "type": "paragraph",
      "html": "And add helper functions to convert:"
    },
    {
      "type": "code",
      "lang": "go",
      "code": "func ToClientProduct(p model.Product) ClientProductResponse {\n    ...\n}\nfunc ToAdminProduct(p model.Product) AdminProductResponse {\n    ...\n}"
    },
    {
      "type": "paragraph",
      "html": "This ensures:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "The same domain logic is used everywhere.",
        "The output is controlled per audience.",
        "No risk of accidentally exposing <code>CostPrice</code> to clients."
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "🔄 Layered Flow: From DB to HTTP"
    },
    {
      "type": "paragraph",
      "html": "Here’s how data flows through the system:"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "Database/In-Memory       ↓repository → Returns model.Product       ↓service    → Applies business\nrules       ↓controller → Converts to dto.AdminProductResponse or dto.ClientProductResponse\n↓HTTP Response (JSON)"
    },
    {
      "type": "paragraph",
      "html": "Each layer has a clear responsibility:"
    },
    {
      "type": "list",
      "ordered": false,
      "items": [
        "Repository: Load/save data.",
        "Service: Validate, compute, orchestrate.",
        "Controller: Handle HTTP, choose DTO, send JSON."
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "🚀 Final Thoughts"
    },
    {
      "type": "paragraph",
      "html": "Go’s simplicity can tempt us to skip structure — but as your app grows, good architecture saves time, reduces bugs, and improves security."
    },
    {
      "type": "heading",
      "level": 3,
      "text": "✅ Key Takeaways:"
    },
    {
      "type": "list",
      "ordered": true,
      "items": [
        "Define domain models once — in <code>internal/model</code>.",
        "Never duplicate structs across layers.",
        "Use DTOs to control API output for different audiences.",
        "Keep the service layer clean — it should use <code>model.Product</code>, not define it.",
        "Let controllers handle presentation, not business logic."
      ]
    },
    {
      "type": "heading",
      "level": 2,
      "text": "✅ Goal: Modular, Microservice-Ready Structure"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Combine Domain + Use Case"
    },
    {
      "type": "paragraph",
      "html": "Instead of separating by role, use DTOs and controllers to handle role-based views — but keep the core in the domain."
    },
    {
      "type": "paragraph",
      "html": "<strong>Example: </strong><code><strong>product</strong></code><strong> The module supports both roles</strong>"
    },
    {
      "type": "code",
      "lang": "text",
      "code": "internal/product/\n├── model/\n│   \n└── product.go              → Full domain model\n├── dto/\n│   \n├──\nclient_product.go       → Public fields\n│   \n└── admin_product.go        → All fields\n├── controller/\n│\n\n├── client_controller.go    → Returns ClientProduct\n│   \n└── admin_controller.go     → Returns\nAdminProduct\n└── service/    \n└── product_service.go      → Business logic (used by both)"
    },
    {
      "type": "paragraph",
      "html": "✅ Same service<br>✅ Same model<br>✅ Two DTOs + Two controllers"
    },
    {
      "type": "paragraph",
      "html": "But one source of truth."
    },
    {
      "type": "embed",
      "provider": "youtube",
      "url": "https://www.youtube.com/embed/aauUEcCcBEY?feature=oembed"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Health Controller"
    },
    {
      "type": "paragraph",
      "html": "go-simple/<br>├── main.go<br>├── internal/<br>│ └── health/<br>│ └── controller/<br>│ └── health_controller.go"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Github Codes"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Swagger Examples"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*ZrIafu4PZb-atJ7bg2PsbA.png",
      "alt": "Building a Clean, Secure, and Scalable Go Web API: A Layered Architecture Guide",
      "caption": "",
      "width": 1325,
      "height": 774
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*gVF2_xzXo7OtLcpr0E_lsw.png",
      "alt": "Building a Clean, Secure, and Scalable Go Web API: A Layered Architecture Guide",
      "caption": "",
      "width": 1325,
      "height": 774
    }
  ]
}
