Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

1 Commit

Folders and files

Repository files navigation

Go Gin Hexagonal REST API Boilerplate

A production-ready, clean, and developer-friendly REST API Boilerplate in Go using Gin and Hexagonal Architecture (Ports & Adapters).

This repository is intentionally designed to be completely generic and decoupled from any specific production business logic. It provides a clean starting point featuring a simple Hello World endpoint and a minimal Example resource so you can quickly build your own microservices.


πŸ“‘ Table of Contents

  1. Architecture Overview
  2. Tech Stack
  3. Key Design Patterns
  4. Directory Reference
  5. Application & Request Flow
  6. Endpoints Reference
  7. Make Commands
  8. Adding a New Feature (Tutorial)
  9. Pre-commit & Security Scans

πŸ› Architecture Overview

This boilerplate follows Hexagonal Architecture (Ports and Adapters):

  • Core Domain lives at the center with zero framework dependencies (pure Go types and rules).
  • Ports (Interfaces) define clear boundaries:
    • Inbound (Driving) Ports: Application use cases called by driving adapters (Gin handlers).
    • Outbound (Driven) Ports: Interfaces describing external dependencies (Postgres database, external REST clients).
  • Adapters implement or call ports:
    • Inbound Adapters: Gin HTTP routing, middleware, request DTOs, HTML status templates.
    • Outbound Adapters: PostgreSQL repository (sqlc), external HTTP client with gobreaker circuit breaker.
flowchart TD
    subgraph DrivingAdapters["Inbound (Driving) Adapters"]
        HTTPClient["HTTP Client / Browser"] -->|REST / JSON| GinRouter["Gin Router & Middlewares<br/>(internal/adapter/inbound/http)"]
        GinRouter --> GinHandler["HTTP Handlers<br/>(handler/example.go)"]
    end

    subgraph HexagonCore["Hexagon Application Core"]
        InboundPort["Inbound Ports<br/>(port/inbound.go)"]
        UseCase["Application Handlers (CQRS)<br/>(commands & queries)"]
        Domain["Pure Domain Entities<br/>(core/domain/example.go)"]
        OutboundPort["Outbound Ports<br/>(port/outbound.go)"]

        GinHandler --> InboundPort
        InboundPort --> UseCase
        UseCase --> Domain
        UseCase --> OutboundPort
    end

    subgraph DrivenAdapters["Outbound (Driven) Adapters"]
        PostgresAdapter["PostgreSQL Adapter<br/>(adapter/outbound/postgres)"]
        SampleAdapter["External REST Client Adapter<br/>(adapter/outbound/client/sample)"]

        OutboundPort -.->|Implements| PostgresAdapter
        OutboundPort -.->|Implements| SampleAdapter

        PostgresAdapter --> PostgresDB[("PostgreSQL Database")]
        SampleAdapter -->|HTTP / JSON (gobreaker)| ExtService["External Microservice"]
    end
Loading

πŸ›  Tech Stack

Component Technology Purpose
Language Go 1.26.8 Compiled, concurrent, type-safe programming language
HTTP Framework Gin High-performance HTTP web framework
API Contract & Gen OpenAPI 3.0 + oapi-codegen Spec-first REST design and typed server generation
Database & Queries PostgreSQL 17 + sqlc Compile-time type-safe Go generated directly from pure SQL
Testing Ginkgo v2 + Gomega BDD-style expressive testing framework
Mocking GoMock (go.uber.org/mock) Compile-time interface mocking for unit tests
Metrics Prometheus (client_golang) Service latency, throughput, and error metrics
Circuit Breaker gobreaker Fault tolerance and circuit tripping for inter-service REST calls
Validation go-playground/validator v10 Struct validation with tags
Profiling Go Standard net/http/pprof Real-time memory, CPU, and goroutine performance profiling
Code Quality pre-commit + golangci-lint Automated git commit hygiene and linting checks
Security Scanning Trivy Vulnerability, misconfiguration, and secret scanning

πŸ’‘ Key Design Patterns

  1. Ports and Adapters: All infrastructure is an adapter. You can swap PostgreSQL for MySQL or Gin for standard net/http without modifying domain entities.
  2. Dependency Inversion Principle (DIP): Use cases depend on Port interfaces; adapters implement those interfaces.
  3. CQRS (Command Query Responsibility Segregation):
    • commands/: Write operations that modify state (e.g., CreateExampleHandler).
    • queries/: Read-only operations optimized for retrieval (e.g., GetExampleHandler).
  4. Circuit Breaker (gobreaker): Prevents cascading failures when communicating with external microservices over REST.
  5. Graceful Shutdown: Catches SIGINT / SIGTERM signals to cleanly drain active requests.

πŸ“‚ Directory Reference

.
β”œβ”€β”€ cmd/
β”‚   └── api/
β”‚       └── main.go                         # Bootstrap, DI wiring, graceful shutdown
β”‚
β”œβ”€β”€ api/
β”‚   └── openapi/
β”‚       β”œβ”€β”€ oapi-codegen.yaml               # Code generation config for oapi-codegen
β”‚       β”œβ”€β”€ server/
β”‚       β”‚   └── spec.yaml                   # OpenAPI 3.0 contract
β”‚       └── client/                         # External microservice specs
β”‚
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ config.go                           # Config loader (env variables with defaults)
β”‚   β”œβ”€β”€ config.yaml                         # Production configuration defaults
β”‚   β”œβ”€β”€ config.local.yaml                   # Local development overrides
β”‚   └── config.integration.yaml             # CI / Integration testing configuration
β”‚
β”œβ”€β”€ db/
β”‚   β”œβ”€β”€ migrations/                         # SQL migration files (.up.sql / .down.sql)
β”‚   β”œβ”€β”€ queries/                            # Source SQL queries for sqlc
β”‚   β”‚   └── examples.sql
β”‚   └── schema.sql                          # Source PostgreSQL schema
β”‚
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ core/
β”‚   β”‚   β”œβ”€β”€ domain/                         # Pure domain logic (entities, validation, errors)
β”‚   β”‚   β”‚   β”œβ”€β”€ example.go                  # Sample domain model
β”‚   β”‚   β”‚   └── errors.go                   # Sentinel domain errors (ErrNotFound, ErrInvalidInput, etc.)
β”‚   β”‚   β”‚
β”‚   β”‚   └── port/                           # Decoupled Port interfaces
β”‚   β”‚       β”œβ”€β”€ inbound.go                  # Use case interfaces (called by Gin handlers)
β”‚   β”‚       β”œβ”€β”€ outbound.go                 # Repository and external client interfaces
β”‚   β”‚       └── mocks/                      # GoMock generated mocks for unit testing
β”‚   β”‚
β”‚   β”œβ”€β”€ application/                        # CQRS Use Case layer
β”‚   β”‚   β”œβ”€β”€ commands/                       # Write use cases
β”‚   β”‚   β”‚   β”œβ”€β”€ create_example.go
β”‚   β”‚   β”‚   └── create_example_test.go      # Ginkgo v2 + Gomega BDD unit tests
β”‚   β”‚   └── queries/                        # Read use cases
β”‚   β”‚       β”œβ”€β”€ get_example.go
β”‚   β”‚       └── list_examples.go
β”‚   β”‚
β”‚   └── adapter/
β”‚       β”œβ”€β”€ inbound/
β”‚       β”‚   └── http/                       # Gin REST driving adapter
β”‚       β”‚       β”œβ”€β”€ handler/                # HTTP controllers (example, health)
β”‚       β”‚       β”œβ”€β”€ middleware/             # Prometheus metrics, auth, logging
β”‚       β”‚       β”œβ”€β”€ dto/                    # Request/Response structs with validator tags
β”‚       β”‚       β”œβ”€β”€ generated/              # Code generated by oapi-codegen
β”‚       β”‚       β”œβ”€β”€ view/                   # Embedded HTML templates (health.html)
β”‚       β”‚       └── router.go               # Gin routing engine, pprof, swagger, and probes
β”‚       β”‚
β”‚       └── outbound/
β”‚           β”œβ”€β”€ postgres/                   # Driven database adapter implementing outbound ports
β”‚           β”‚   β”œβ”€β”€ sqlc/                   # Go code generated by sqlc
β”‚           β”‚   └── repository.go
β”‚           └── client/
β”‚               └── sample/                 # Generic REST client with gobreaker circuit breaker
β”‚                   └── client.go
β”‚
β”œβ”€β”€ tests/
β”‚   └── integration/                        # Integration and E2E tests
β”‚
β”œβ”€β”€ Dockerfile                              # Multi-stage production container (Go 1.26.8 -> Alpine)
β”œβ”€β”€ Dockerfile.dev                          # Live-reload container with Air
β”œβ”€β”€ compose.yaml                            # Local Docker Compose setup (API + PostgreSQL)
β”œβ”€β”€ Makefile                                # Task automation (build, test, lint, gen, security)
β”œβ”€β”€ sqlc.yaml                               # sqlc configuration
└── .pre-commit-config.yaml                 # Git hooks (golangci-lint, go-vet, trivy)

πŸ”„ Application & Request Flow

sequenceDiagram
    autonumber
    actor Client as HTTP Client
    participant Gin as Gin Inbound Adapter<br/>(router.go)
    participant MW as Prometheus & Recovery Middleware
    participant Handler as ExampleHandler<br/>(handler/example.go)
    participant UC as CreateExampleHandler<br/>(application/commands)
    participant Domain as Example Entity<br/>(core/domain)
    participant Repo as Postgres Repository<br/>(adapter/outbound/postgres)
    participant Ext as External Client (gobreaker)

    Client->>Gin: POST /v1/examples (JSON)
    Gin->>MW: Process Request
    MW->>Handler: Route to Create(c)
    Handler->>Handler: Bind & Validate DTO (go-playground/validator)
    Handler->>UC: Execute(ctx, CreateExampleCommandDTO)
    UC->>Domain: NewExample(name, description)
    Domain-->>UC: Return Validated *domain.Example
    UC->>Repo: Create(ctx, example)
    Repo-->>UC: Persisted in DB
    UC->>Ext: Notify(ctx, "created") [Circuit Breaker]
    Ext-->>UC: Success
    UC-->>Handler: Return *domain.Example
    Handler->>Handler: Map to DTO (FromDomain)
    Handler-->>Client: 201 Created (JSON)
Loading

πŸ“‘ Endpoints Reference

Boilerplate & Sample Routes

Endpoint Method Description Example Request / Query
/v1/hello GET Simple Hello World endpoint /v1/hello?name=Developer
/v1/examples POST Creates an example item {"name": "Item 1", "description": "Sample"}
/v1/examples GET Lists all example items /v1/examples
/v1/examples/:id GET Retrieves an example item by UUID /v1/examples/550e8400-e29b-41d4-a716-446655440000

Health & Observability Endpoints

Endpoint Method Purpose Response
/healthz GET Liveness Probe: Confirms process is running 200 OK: {"status":"alive"}
/readyz GET Readiness Probe: Verifies database connection 200 OK or 503 Service Unavailable
/health GET Dashboard: JSON for APIs, embedded HTML status card in browsers JSON or HTML view
/metrics GET Prometheus Metrics: Request latency, status counts, in-flight requests Prometheus text format
/debug/pprof/* GET Go Profiling: Heap allocations, CPU profiling, goroutines pprof data
/swagger/* GET Swagger UI: Interactive OpenAPI documentation HTML UI

⚑ Make Commands

Run make help to inspect available automation tasks:

Usage:
  make <target>

Targets:
  help                 Display this help screen
  tidy                 Download and tidy Go dependencies
  build                Build the Go API binary
  run                  Run the API service locally
  test                 Run all tests using Ginkgo v2 with Gomega
  test-unit            Run unit tests only
  test-integration     Run integration tests only
  mock                 Generate mock files using GoMock (mockgen)
  sqlc                 Generate database code using sqlc
  oapi                 Generate Gin server and models from OpenAPI 3 spec
  lint                 Run golangci-lint
  trivy                Scan repository vulnerabilities and misconfigurations using Trivy
  pre-commit           Run all pre-commit hooks
  docker-build         Build the production Docker image
  docker-up            Start Docker Compose services
  docker-down          Stop Docker Compose services
  clean                Clean build artifacts

πŸ“– Adding a New Feature (Tutorial)

Let's walk through adding a new resource: Tasks (tasks table).

Step 1: Database Table & sqlc Query

  1. Update db/schema.sql:
    CREATE TABLE IF NOT EXISTS tasks (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        title VARCHAR(255) NOT NULL,
        completed BOOLEAN NOT NULL DEFAULT FALSE,
        created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    );
  2. Create db/queries/tasks.sql:
    -- name: CreateTask :one
    INSERT INTO tasks (title) VALUES ($1) RETURNING *;
    
    -- name: ListTasks :many
    SELECT * FROM tasks ORDER BY created_at DESC;
  3. Run make sqlc to generate type-safe Go code.

Step 2: Domain Entity & Outbound Port

  1. Create internal/core/domain/task.go:
    package domain
    
    type Task struct {
        ID        uuid.UUID
        Title     string
        Completed bool
    }
  2. In internal/core/port/outbound.go, declare the repository interface:
    type TaskRepository interface {
        Create(ctx context.Context, task *domain.Task) error
        List(ctx context.Context) ([]*domain.Task, error)
    }

Step 3: Repository Outbound Adapter

In internal/adapter/outbound/postgres/repository.go, implement TaskRepository using the generated sqlc queries.


Step 4: Application Command / Query

Create internal/application/commands/create_task.go:

package commands

type CreateTaskHandler struct {
    repo port.TaskRepository
}

func (h *CreateTaskHandler) Execute(ctx context.Context, title string) (*domain.Task, error) {
    task := &domain.Task{ID: uuid.New(), Title: title, Completed: false}
    if err := h.repo.Create(ctx, task); err != nil {
        return nil, err
    }
    return task, nil
}

Step 5: Inbound Driving Port & HTTP Handler

  1. Declare the driving port in internal/core/port/inbound.go:
    type CreateTaskUseCase interface {
        Execute(ctx context.Context, title string) (*domain.Task, error)
    }
  2. Create internal/adapter/inbound/http/handler/task.go to handle POST /v1/tasks.

Step 6: Route Registration & DI Wiring

  1. In internal/adapter/inbound/http/router.go, register the route:
    v1.POST("/tasks", taskHandler.Create)
  2. In cmd/api/main.go, instantiate and wire dependencies.

Step 7: BDD Unit Testing with Ginkgo & Gomock

Run make mock to update mocks, then test your use case in internal/application/commands/create_task_test.go:

var _ = Describe("CreateTaskHandler", func() {
    It("creates a task", func() {
        mockRepo.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil)
        res, err := handler.Execute(ctx, "Sample Task")
        Expect(err).NotTo(HaveOccurred())
        Expect(res.Title).To(Equal("Sample Task"))
    })
})

Run make test to verify!


πŸ”’ Pre-commit & Security Scans

Before committing code, verify quality gates:

# Run security vulnerability & secret scan
make trivy

# Run linter
make lint

# Run all pre-commit hooks
make pre-commit

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages