Browse by type
A production-ready Go backend template for services that do more than serve HTTP.
One binary. Multiple processes. Zero boilerplate.
Most Go service templates stop at "here is how to start an HTTP server." Real production services are more complex: they run background jobs, consume Kafka or RabbitMQ messages, expose gRPC endpoints for internal traffic, and handle one-off data migrations — all sharing the same business logic and infrastructure.
This template distils patterns and lessons from multiple production Go services. Rather than reflecting a single codebase, it combines the architectural decisions that proved scalable across different domains and team sizes into one reusable starting point that:
internal/ and your domain in internal/app/, making the boundary between your code and plumbing explicitThe template is intentionally opinionated. It picks go-kit, gorilla/mux, sqlx, zerolog, and viper — a stack that has been proven at scale. You are free to swap any layer out.
go-service-starter-kit/
│
├── cmd/
│ ├── main.go # Entry point — flag parsing, process dispatch
│ └── app/
│ ├── registry.go # Long-running process registry
│ ├── http.go # HTTP process wiring
│ ├── grpc.go # gRPC process wiring
│ ├── consumer.go # Message consumer wiring
│ └── job.go # One-time job registry
│
├── api/ # Proto module (github.com/nawafswe/go-service-starter-kit/api)
│ ├── go.mod # Nested Go module — import with: go get …/api@v1.0.0
│ └── proto/grpc/v1/
│ ├── example.proto # proto3 service definitions
│ └── gen/ # Generated Go stubs (pb.go + grpc.pb.go)
│
├── internal/
│ ├── auth/ # JWT (ClaimsParser) + bcrypt password hashing
│ ├── clients/
│ │ └── db/
│ │ ├── postgres/ # OTel-traced PostgreSQL pool (sqlx + lib/pq)
│ │ ├── mysql/ # OTel-traced MySQL pool (sqlx + go-sql-driver)
│ │ └── mongodb/ # OTel-traced MongoDB client (mongo-driver)
│ ├── config/ # Viper loader — YAML + .env + env vars
│ ├── db/ # Database-agnostic pagination (Page, PageResult)
│ │ └── sqlorder/ # SQL ORDER BY builder with column sanitisation
│ ├── gokit/
│ │ ├── http/ # go-kit HTTP handler factory
│ │ ├── grpc/ # go-kit gRPC handler factory
│ │ └── consumer/ # go-kit endpoint wrapper for message consumers
│ ├── httperrors/ # Reusable HTTP error types (400, 401, 403, 404, 409, 500)
│ ├── httpx/ # Resilient HTTP client (retry, circuit breaker, OTel)
│ │ └── mock/ # MockDoer for unit tests
│ ├── grpcx/ # Resilient gRPC client (retry, circuit breaker, OTel)
│ │ └── mock/ # MockInvoker for unit tests
│ ├── middleware/
│ │ ├── http.go # JWT HTTP middleware (AuthRequired / AuthOptional / AuthMock)
│ │ ├── grpc.go # JWT gRPC interceptors + logging + tracing interceptors
│ │ ├── consumer.go # JWT consumer middleware (works with any message broker)
│ │ ├── gokit.go # Timeout + sliding-window rate limiter (go-kit)
│ │ └── logging.go # Transport-aware logging with sensitive-field masking
│ ├── observability/
│ │ ├── logger/ # zerolog-backed structured, context-aware logger
│ │ ├── tracing/ # OTel trace provider (OTLP gRPC exporter)
│ │ └── metric/ # OTel metric Reporter
│ ├── text/ # NonLoggable — redacts sensitive strings from logs and JSON
│ ├── worker/
│ │ ├── http.go # HTTP worker — graceful shutdown on SIGINT/SIGTERM
│ │ ├── grpc.go # gRPC worker — graceful shutdown
│ │ ├── consumer.go # Consumer worker — graceful shutdown
│ │ └── mock/ # MockMessageConsumer for unit tests
│ │
│ └── app/ # Your domain code lives here
│ ├── domain/ # Entities + sentinel errors
│ ├── business/ # Use cases — one file per operation
│ ├── repositories/ # Data access layer (sqlx + PostgreSQL)
│ ├── endpoint/v1/ # go-kit endpoint adapters
│ └── transport/
│ ├── http/ # HTTP — server, bootstrap, v1 encode/decode, JSON:API error encoder
│ ├── grpc/ # gRPC — server, bootstrap, v1 handler + encode/decode via go-kit
│ └── consumer/ # Consumer — bootstrap, v1 message decode, map-based endpoint routing
│
├── test/ # Integration + load tests (isolated from internal/)
│ ├── .env.integration # Test-specific environment variables
│ ├── pkg/
│ │ ├── suite/ # Test suite — per-test DB isolation, token helpers, HTTP client
│ │ └── testdb/ # Isolated PostgreSQL provisioning (golang-migrate)
│ ├── api/
│ │ └── http/
│ │ └── example/ # Example endpoint integration tests + testdata
│ └── load/ # k6 load test scripts
│ ├── data/ # Request payload templates (JSON)
│ ├── pkg/ # Shared config, utilities, gRPC client wrapper + proto
│ ├── http_*.js # HTTP endpoint load tests
│ └── rpc_*.js # gRPC endpoint load tests
│
├── scripts/
│ └── k6.sh # k6 runner — lists and runs load tests
│
├── db/
│ ├── initdb.d/ # PostgreSQL init scripts (user/schema bootstrap)
│ ├── load/ # Seed / fixture data
│ └── migrations/ # SQL migration files (golang-migrate)
│
├── docs/
│ ├── asyncapi/ # AsyncAPI 3.0 spec — event / message contracts
│ ├── img/ # Assets used in documentation
│ └── openapi/ # OpenAPI 3.1 spec + oapi-codegen config
│
├── .github/workflows/ci.yml # CI — build + unit tests + integration tests on every push / PR
├── docker-compose.yml # postgres, kafka, otel-collector (profile-based)
├── otel-collector-config.yaml # OpenTelemetry Collector configuration
├── config.yaml # Default configuration
├── .env.sample # Environment variable template
├── Dockerfile # Production multi-stage build
├── Dockerfile.dev # Development build
├── Makefile # Developer commands
└── LICENSE
The design follows a strict dependency flow — outer layers depend on inner layers, never the reverse:
Request
│
▼
Transport (HTTP / gRPC / Consumer)
│ encode / decode
▼
Endpoint (go-kit adapter — applies middleware: auth, timeout, rate-limit, logging)
│ typed request
▼
Business (use-case handler — pure Go, no framework dependency)
│ repository interface
▼
Repository (sqlx + PostgreSQL — implements the interface)
│
▼
Database
Each layer communicates through interfaces, which means every layer can be unit-tested in isolation with mocks — no database, no HTTP server required.
git clone https://github.com/nawafswe/go-service-starter-kit.git my-service
cd my-service
# Rename the Go module to match your repository
go mod edit -module github.com/<your-org>/<your-service>
go mod tidy
# Copy sample env and fill in your values
make env
Important: update all import paths after renaming the module. A global search-and-replace of
github.com/nawafswe/go-service-starter-kitwith your new module path covers everything.
docker compose up postgres -d
docker compose up migrate # runs all pending migrations
make build
./bin/app http # HTTP server (default :8080)
./bin/app grpc # gRPC server (default :50051)
./bin/app consumer # message consumer
./bin/app <job> # one-time job
| Command | Description |
|---|---|
make build |
Build the binary to ./bin/app |
make build-docker |
Build the production Docker image |
make run-http |
Build and run the HTTP server |
make run-grpc |
Build and run the gRPC server |
make run-consumer |
Build and run the message consumer |
make env |
Copy .env.sample to .env if it doesn't exist |
make clean |
Remove built binaries |
make migrate-up |
Run all pending database migrations |
make migrate-create name=<name> |
Create a new migration file |
make lint |
Run golangci-lint (via Docker) |
make test |
Run unit tests with coverage |
make test-integration |
Run integration tests |
make test-load name=<test> |
Run a k6 load test (e.g. K6_VUS=100 make test-load name=http_post_create_example) |
make test-load-list |
List available k6 load tests |
make fmt |
Format code (gci + gofumpt) |
make generate |
Run go generate across all packages |
make generate-contracts |
Regenerate HTTP types from OpenAPI spec |
make docker-start |
Start the Docker environment |
make docker-stop |
Stop the Docker environment |
make docker-clean |
Remove Docker containers and volumes |
make docker-restart |
Restart the Docker environment |
Configuration is merged from three sources (highest priority first):
| Priority | Source | Format |
|---|---|---|
| 1 (highest) | OS environment variables | KEY__NESTED=value |
| 2 | .env file |
KEY__NESTED=value |
| 3 (lowest) | config.yaml |
YAML |
The __ double-underscore is the struct delimiter — DB__DSN maps to Config.DB.DSN.
Required variables:
| Variable | Description |
|---|---|
DB__DSN |
PostgreSQL connection string |
JWT__SECRET |
HMAC-SHA256 signing secret |
HTTP__PORT |
HTTP listen port |
Each endpoint can be individually tuned for timeout and rate limiting in config.yaml:
ENDPOINTS:
EXAMPLE_CREATE:
DEADLINE: 5s # request timeout
RATE_LIMITER:
INTERVAL: 1m # sliding window duration
LIMIT: 100 # max requests per window
These values are applied as go-kit middleware in each transport's bootstrap layer.
Services are organised into profiles so you only run what you need:
| Profile | Services | Command |
|---|---|---|
| (default) | postgres, migrate |
docker compose up |
app |
app-http, app-grpc, app-consumer |
docker compose --profile app up |
kafka |
kafka (single-node KRaft) |
docker compose --profile kafka up |
observability |
otel-collector |
docker compose --profile observability up |
Combine profiles as needed: docker compose --profile app --profile kafka --profile observability up
Replace internal/app/ with your service name (e.g. internal/orders/) and update the import paths. The shared infrastructure packages under internal/ stay unchanged.
internal/<domain>/
domain/ <- add your entity + any new sentinel errors
repositories/<x>/ <- add your SQL queries
business/<op>/ <- add your handler (CreateXxx, UpdateXxx, ...)
endpoint/v1/ <- add your go-kit endpoint adapter
transport/http/v1/ <- add your encode/decode codec
Then wire it in internal/app/transport/http/bootstrap/:
1. handler_initializer.go — instantiate the handler
2. router_v1_register.go — mount the route
// cmd/app/my_process.go
type MyProcess struct{}
func (MyProcess) Register(args ProcessArgs) (Process, error) { ... }
// cmd/app/registry.go
var RegistryProcessesMap = map[string]ProcessRegistry{
"http": NewHTTPServerProcess(),
"my-process": MyProcess{}, // <- add here
}
// internal/app/jobs/my_job.go
type MyJob struct{}
func (MyJob) Schedule(args app.ProcessArgs) error { ... }
// cmd/app/job.go
var JobsMap = map[string]Scheduler{
"my-job": MyJob{},
}
Run it with: ./bin/app my-job
The consumer transport is broker-agnostic — it defines a MessageRouter that maps message types to go-kit endpoints with decode functions. You provide the broker integration:
```go // internal/app/transport/consumer/consumer.go — Start() reader := kafka.NewReader(kafka.ReaderConfig{ Brokers: c.cfg.Consumer.Brokers, GroupID: c.cfg.Consumer.GroupID, Topic: c.cfg.Consumer.Topics[0], }) defer reader.Close()
for { msg, err := reader.ReadMessage(ctx) if err != nil { if errors.Is(err, context.Canceled) { return nil } return fmt.Errorf("consumer: read message: %w", err)
browse all types & interfaces →
$ claude mcp add go-service-starter-kit \
-- python -m otcore.mcp_server <graph>