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.
- Architecture Overview
- Tech Stack
- Key Design Patterns
- Directory Reference
- Application & Request Flow
- Endpoints Reference
- Make Commands
- Adding a New Feature (Tutorial)
- Pre-commit & Security Scans
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 withgobreakercircuit 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
| 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 |
- Ports and Adapters: All infrastructure is an adapter. You can swap PostgreSQL for MySQL or Gin for standard
net/httpwithout modifying domain entities. - Dependency Inversion Principle (DIP): Use cases depend on Port interfaces; adapters implement those interfaces.
- CQRS (Command Query Responsibility Segregation):
commands/: Write operations that modify state (e.g.,CreateExampleHandler).queries/: Read-only operations optimized for retrieval (e.g.,GetExampleHandler).
- Circuit Breaker (
gobreaker): Prevents cascading failures when communicating with external microservices over REST. - Graceful Shutdown: Catches
SIGINT/SIGTERMsignals to cleanly drain active requests.
.
βββ 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)
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)
| 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 |
| 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 |
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 artifactsLet's walk through adding a new resource: Tasks (tasks table).
- 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() );
- 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;
- Run
make sqlcto generate type-safe Go code.
- Create
internal/core/domain/task.go:package domain type Task struct { ID uuid.UUID Title string Completed bool }
- 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) }
In internal/adapter/outbound/postgres/repository.go, implement TaskRepository using the generated sqlc queries.
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
}- Declare the driving port in
internal/core/port/inbound.go:type CreateTaskUseCase interface { Execute(ctx context.Context, title string) (*domain.Task, error) }
- Create
internal/adapter/inbound/http/handler/task.goto handlePOST /v1/tasks.
- In
internal/adapter/inbound/http/router.go, register the route:v1.POST("/tasks", taskHandler.Create)
- In
cmd/api/main.go, instantiate and wire dependencies.
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!
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