go-clean-template: a Go service skeleton with four transports
Clean Architecture template for Golang services
At a glance
- What is it?
- Evrone's Clean Architecture template wires user auth, task management and translation over REST, gRPC, RabbitMQ and NATS at once. Here is what it actually sets up, and what it costs you to adopt.
- Who is it for?
- go-clean-template is worth copying rather than depending on. The `tool` block in go.mod, the Makefile-first workflow documented in the v1.18.0 release notes, and the scratch-based Dockerfile are the parts with lasting value, and they cost nothing to take.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 17 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why the project exists and who maintains it
The README is blunt about its purpose. The template exists to show how to organize a Go project before it turns into spaghetti code, where to put business logic so it stays independent, and how to avoid losing control of a microservice as it grows. It credits the principles of Robert Martin and says plainly that Evrone created and supports it, which is worth knowing before you build on it: this is a company-maintained opinionated skeleton rather than a community standard that will outlive its sponsor.
It is also worth being precise about what a template is here. Nothing in this repository is meant to be imported as a library. What you take from it is a directory layout, a dependency wiring style, and a set of decisions someone already made about logging, migrations, config loading and tracing. The repository topics say the same thing in four words: clean-architecture, dependency-injection, example, template. The word example is the honest one.
The licence is MIT, so lifting the code carries no obligations. The default branch is master, the language is Go, and the module declares itself as `github.com/evrone/go-clean-template`. The last push was on 2026-09-19, the same day v1.19.0 shipped, which was a dependency bump of Apache Thrift plus a move to Go 1.27.1 and assorted security updates. The previous release, v1.18.0 on 2026-08-22, added `CLAUDE.md` and an `AGENTS.md` symlink pointing at it, described in its notes as a vendor-neutral instruction file for coding agents. That is a small signal, but a telling one: whoever maintains this treats tooling friction as a real problem worth solving in the repository.
Four transports over the same three domains
This is the part that distinguishes the template from the many Go skeletons that ship one HTTP router and call it a day. The project implements four server types: AMQP RPC over RabbitMQ using the Request-Reply messaging pattern, MQ RPC over NATS with the same pattern, gRPC with protobuf, and a REST API on Fiber. Three demo domains then run across all four: user authentication with registration, login and JWT authorization, task management with CRUD and a status machine, and text translation with history tracking.
The README documents the surface in paired tables so you can see that the business logic is written once. `POST /v1/auth/register` maps to `AuthService/Register`, and `POST /v1/tasks` maps to `TaskService/CreateTask`. Status changes are a separate endpoint, `PATCH /v1/tasks/:id/status`, which calls `TaskService/TransitionTask`, and the allowed transitions are `todo` to `in_progress` to `done`, with `in_progress` back to `todo`. Tasks are scoped to the authenticated user, and listing supports `limit`/`offset` pagination with an optional status filter. Passwords are hashed with bcrypt, the JWT expiry is configurable, and auth middleware runs on every transport.
This is the Clean Architecture claim made concrete rather than asserted. Because the same translation operation is reachable as `POST /v1/translation/do-translate` over HTTP and as `TranslationHistoryService/DoTranslate` over gRPC, the transport-specific code is confined to the edge and the use case sits underneath it once. Whether four transports is the right number for your service is a different question, addressed further on.
Bringing the stack up with make compose-up
The quick start is Makefile targets rather than raw compose commands, and the v1.18.0 release notes explain why that is not laziness. The `CLAUDE.md` file added in that release documents a Makefile-first workflow with a per-task table mapping each job to its `make` target, and it names the flags you lose by calling the underlying tool directly: `make test` adds `-race -covermode atomic`, among others. The intent is that the Makefile is the public interface of the repository and the commands behind it are implementation.
Local development starts the backing services first, then runs the app with migrations applied:
# Postgres, RabbitMQ, NATS
make compose-up
# Run app with migrations
make runThe integration test variant is a separate target designed to run in CI, matching the `docker-compose-integration-test.yml` file that sits at the repository root next to the main compose file:
# DB, app + migrations, integration tests
make compose-up-integration-testA third target, `make compose-up-all`, brings up the full stack behind the reverse proxy configured in the `nginx/` directory. Once it is running, the README lists the addresses you can check, and they are worth reading for one reason: the compose file gives every service an `lvh.me` network alias alongside its port mapping, so `app.lvh.me`, `grpc.lvh.me`, `rabbitmq.lvh.me`, `nats.lvh.me` and `jaeger.lvh.me` all resolve to 127.0.0.1 without editing a hosts file. Health, metrics and Swagger sit on port 8080, gRPC on 8081, the RabbitMQ management UI on 15672, NATS monitoring on 8222, and the Jaeger trace UI on 16686.
Configuration as environment variables read through struct tags
There is no config file format to learn. The repository ships `.env.example`, and every setting the application reads is listed there, from `HTTP_PORT` and `HTTP_USE_PREFORK_MODE` through `LOG_LEVEL`, `RMQ_URL`, `NATS_URL`, `METRICS_ENABLED`, `SWAGGER_ENABLED` and the tracing block, to `JWT_SECRET` and `JWT_TOKEN_EXPIRY`. The `go.mod` pins `github.com/caarlos0/env/v11` at v11.4.1, so the mechanism is struct tags parsed at startup rather than a hierarchical config file.
Three of those values are worth pausing on because they ship with placeholder content you must change. `JWT_SECRET` is literally set to `your-secret-key-change-in-production`. `POSTGRES_SSL_MODE` is `disable` in the compose defaults. The password is a fixed string used in both the database service and the connection URL, which is fine for a laptop and wrong anywhere else.
PG_POOL_MAX=2
PG_URL=postgres://user:myAwEsOm3pa55%40w0rd@localhost:5432/db
JWT_SECRET=your-secret-key-change-in-productionThe toolchain section is the more interesting part of go.mod. Go 1.27 supports a `tool` directive, and this repository uses it to pin nine development tools inside the module rather than expecting them on your PATH: `gci` for import grouping, `gofumpt`, `golangci-lint/v2` matched by a `.golangci.yml` at the root, `swag` for generating the Swagger UI, `mockgen`, `govulncheck`, the `migrate` CLI, and the two protobuf plugins. Any of them then runs at the exact version the project tested, which removes the usual argument about which linter version produced which complaint.
A scratch image and a migration binary built into the app
The Dockerfile is a three-stage build ending in an image with nothing in it. The first stage copies only `go.mod` and `go.sum` and runs `go mod download`, so dependency resolution is cached independently of your source changes. The second stage builds the binary for a fixed target with CGO switched off:
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -tags migrate -o /bin/app ./cmd/appThe `migrate` build tag is the detail that matters. It pulls the `golang-migrate` CLI into the same binary, so the running container can apply migrations from the `migrations/` directory it ships with, without a second tool or an init container. The final stage copies the binary, the `config/` directory, the `migrations/` directory and the CA certificate bundle into `scratch`, which means the deployed image contains no shell, no package manager and no libc.
Instrumentation is wired at the framework level rather than by hand in each handler. `fiberprometheus` exposes metrics, `otelfiber` traces HTTP requests, `otelpgx` traces database calls, `otelgrpc` traces gRPC, and the OTLP exporter sends spans to Jaeger. The compose file points `TRACING_OTLP_ENDPOINT` at `jaeger:4317` with `TRACING_OTLP_INSECURE` set to true, and sets `TRACING_SAMPLE_RATE` to `1.0` with an inline comment warning against that value in production and suggesting 0.1 or 0.01 instead. The comment is worth reading as a small piece of honesty about what a demo default costs at scale.
What it costs to adopt, and where it stops helping
The honest limitation is width. go.mod pins pgx, Squirrel, Fiber, nats.go, amqp091-go, zerolog, the full OpenTelemetry SDK, gRPC, protobuf, swaggo, go-playground validator, goccy/go-json, golang-jwt, golang-migrate, testify, mock and uuid. Each of those is a decision you inherit along with the layout, and the three demo domains are code you will delete. Users, tasks and translation survive contact with your product only if your product happens to be a task list.
The opinionated HTTP choice cuts both ways too. Fiber is not net/http, and the tracing and metrics integrations attach to Fiber's middleware chain, so swapping in Echo or chi means rewriting the edge. That is a normal outcome for this kind of template and a real cost if you wanted the standard library.
There is also the critique the architecture itself attracts, which the README does not engage with. Layering that keeps business logic testable also inserts indirection in front of a handler that does three lines of work, and for a small service the ceremony costs more than it saves. That argument is legitimate, and the template's answer is really its breadth: it pays off when a service genuinely has to be reachable four ways, and it is dead weight when it does not.
One gap is documentation rather than code. The README's observability section stops after the sentence introducing distributed tracing, so the details of the tracing setup, the metric names and the log format live in `docs/` rather than on the front page. The README is also translated into Chinese and Russian, which is a good sign of the audience but means the English copy is the one most likely to lag behind the code.
Editorial conclusion
go-clean-template is worth copying rather than depending on. The `tool` block in go.mod, the Makefile-first workflow documented in the v1.18.0 release notes, and the scratch-based Dockerfile are the parts with lasting value, and they cost nothing to take. What you skip is the four-transport sample: if your service speaks HTTP alone, the AMQP and NATS adapters plus the RabbitMQ and NATS services in docker-compose.yml are infrastructure you are maintaining for a demo. Start by reading the Makefile, since that is the interface the project intends you to use, then decide whether one transport or four matches the service you are actually building.
Frequently asked questions
Does go-clean-template use net/http or a third-party router?
It uses Fiber, pinned in go.mod as github.com/gofiber/fiber/v2 at v2.52.15. The README also lists Fiber as the web framework in its badge row, and adds gofiber/contrib/otelfiber for request tracing and gofiber/swagger for the generated API docs. Swapping the HTTP layer means touching the transport adapters, since the tracing and metrics middleware attach to Fiber's own chain.
What does make compose-up start in go-clean-template?
It starts Postgres, RabbitMQ and NATS, which is exactly what the comment above the target in the README says. A second target, make run, then starts the application and applies migrations, and make compose-up-integration-test brings up the database, the app with migrations and the integration tests for CI runs. The compose file also adds lvh.me hostnames so the services are reachable through names like app.lvh.me without a hosts file edit.
Which Go version does go-clean-template target?
go.mod declares go 1.27, and the Dockerfile builds on golang:1.27-alpine3.23. Release v1.19.0, published on 2026-09-19, is a move to Go 1.27.1 alongside dependency bumps and security updates. The module also uses the Go tool directive to pin its linters, formatter and protobuf plugins at known versions.
Does go-clean-template need a separate migration tool at runtime?
No. The Dockerfile builds the binary with the migrate build tag, which pulls the golang-migrate CLI into the same executable, and the final scratch image copies the migrations directory alongside it. That means the container can apply schema changes on startup without a second image or an init container.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/evrone-go-clean-template)