Created
March 14, 2026 17:42
-
-
Save IceWreck/8d4722db8cbd43ee4d4adf506736617b to your computer and use it in GitHub Desktop.
Go Boilerplate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Go Boilerplate | |
| ### Init | |
| In the empty git repo, init with `go mod init github.com/username/projectname`. | |
| The repo tree should look something like this. | |
| ``` | |
| . | |
| ├── AGENTS.md | |
| ├── Dockerfile | |
| ├── Makefile | |
| ├── README.md | |
| ├── bin | |
| ├── cmd | |
| │ └── appname | |
| │ └── main.go | |
| ├── config | |
| │ └── example.env | |
| ├── go.mod | |
| ├── go.sum | |
| └── pkg | |
| ├── config | |
| │ ├── config.go | |
| │ ├── constants.go | |
| │ └── version.go | |
| └── logger | |
| └── logger.go | |
| ``` | |
| ### Agents MD | |
| `AGENTS.md` file also symlink to `CLAUDE.md`. | |
| ```markdown | |
| # Agents Knowledge | |
| ## Architecture | |
| TODO | |
| ## Coding Conventions | |
| - Use the stdlib's slog package for logging. | |
| - The log message should always start with lowercase and should not end with a period. Example: `slog.Info("info message")` | |
| - Use early returns to reduce indentation. | |
| - Extract complex logic into functions. | |
| - Leverage data structures instead of deeply nested conditions. | |
| - Write self-explanatory code – Prefer clear variable and function names over comments. | |
| - Explain "why," not "what" – Comments should clarify intent, not restate code. | |
| - Avoid redundant comments – Don't comment obvious things. | |
| - Use comments for complex logic – Explain non-trivial decisions or workarounds. | |
| - Write package, type, function, and method comments as full sentences; start with the name being described. | |
| - Avoid premature abstractions; add interfaces or patterns only when they clarify behavior. | |
| - While writing Go, accept interfaces and return types unless there is a clear reason not to do so. | |
| ## Formatting, Linting & Building | |
| After significant code changes, format, lint and vet with: | |
| ``` | |
| make check | |
| ``` | |
| Build with: | |
| ``` | |
| make build | |
| ``` | |
| ``` | |
| ### Makefile | |
| ```makefile | |
| #!make | |
| -include .env | |
| export $(shell test -f .env && sed 's/=.*//' .env) | |
| SHELL := /bin/bash | |
| IMAGE ?= docker.io/username/appname | |
| VERSION_PKG := github.com/username/appname/pkg/config | |
| GIT_TAG := $(shell git describe --tags --abbrev=0 2>/dev/null || true) | |
| GIT_SHA := $(shell git rev-parse --short HEAD 2>/dev/null || true) | |
| BUILD_DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) | |
| LDFLAGS := -s -w -X $(VERSION_PKG).gitTag=$(GIT_TAG) -X $(VERSION_PKG).gitSha=$(GIT_SHA) -X $(VERSION_PKG).buildDate=$(BUILD_DATE) | |
| .PHONY: * | |
| run: | |
| go run ./cmd/appname | |
| build: | |
| touch .env && go build -v -ldflags "$(LDFLAGS)" -o ./bin/appname ./cmd/appname | |
| fmt: | |
| go fmt ./... | |
| goimports -w . | |
| vet: | |
| go vet ./... | |
| clean: | |
| rm -rf ./bin | |
| deps: | |
| go mod download | |
| go mod tidy | |
| deps-update: | |
| go get -u ./... | |
| go mod tidy | |
| tools: | |
| go install golang.org/x/tools/cmd/goimports@latest | |
| check: fmt vet | |
| container-build: | |
| touch .env && podman build --build-arg GIT_TAG="$(GIT_TAG)" --build-arg GIT_SHA="$(GIT_SHA)" --build-arg BUILD_DATE="$(BUILD_DATE)" -t $(IMAGE) . | |
| container-push: | |
| podman push $(IMAGE) | |
| ``` | |
| ### Main | |
| Just a hello world using the logger and urfave/cli for CLI creation. | |
| It should load config and then log it. | |
| ```go | |
| package main | |
| import ( | |
| "context" | |
| "log/slog" | |
| "os" | |
| "github.com/username/appname/pkg/config" | |
| "github.com/username/appname/pkg/logger" | |
| "github.com/urfave/cli/v3" | |
| ) | |
| func main() { | |
| logger.SetupLogging() | |
| cmd := &cli.Command{ | |
| Name: "appname", | |
| Usage: "load config and print it", | |
| Version: config.Version, | |
| Action: run, | |
| } | |
| if err := cmd.Run(context.Background(), os.Args); err != nil { | |
| slog.Error("command failed", slog.Any("err", err)) | |
| os.Exit(1) | |
| } | |
| } | |
| func run(_ context.Context, _ *cli.Command) error { | |
| cfg, err := config.Load() | |
| if err != nil { | |
| return err | |
| } | |
| slog.Info("loaded config", slog.Any("config", cfg)) | |
| return nil | |
| } | |
| ``` | |
| ### Config | |
| ```go | |
| package config | |
| import ( | |
| "github.com/caarlos0/env/v11" | |
| ) | |
| type Config struct { | |
| Server struct { | |
| Port string `env:"PORT" envDefault:"8080"` | |
| Host string `env:"HOST" envDefault:"0.0.0.0"` | |
| } | |
| } | |
| func Load() (*Config, error) { | |
| var cfg Config | |
| if err := env.Parse(&cfg); err != nil { | |
| return nil, err | |
| } | |
| return &cfg, nil | |
| } | |
| ``` | |
| ### Version | |
| ```go | |
| package config | |
| import ( | |
| "fmt" | |
| "strings" | |
| ) | |
| var ( | |
| gitTag string | |
| gitSha string | |
| buildDate string | |
| ) | |
| // Version holds the version string, with tag and git sha info. | |
| // Examples: | |
| // dev | |
| // v0.2.0 (5b84188, 2026-03-14T17:00:00Z) | |
| // v0.3.2-SNAPSHOT (715f552, 2026-03-14T17:00:00Z) | |
| // master (9ed35cb, 2026-03-14T17:00:00Z) | |
| var Version = func() string { | |
| if gitSha == "" { | |
| return "dev" | |
| } | |
| gitTag = strings.TrimPrefix(gitTag, "v") | |
| if buildDate == "" { | |
| return fmt.Sprintf("%s (%s)", gitTag, gitSha) | |
| } | |
| return fmt.Sprintf("%s (%s, %s)", gitTag, gitSha, buildDate) | |
| }() | |
| ``` | |
| ### Logger | |
| ```go | |
| package logger | |
| import ( | |
| "log/slog" | |
| "os" | |
| "strings" | |
| ) | |
| // SetupLogging configures the global slog logger with sensible defaults. | |
| func SetupLogging() { | |
| logLevel := os.Getenv("LOG_LEVEL") | |
| logFormat := os.Getenv("LOG_FORMAT") | |
| level := slog.LevelInfo | |
| if logLevel != "" { | |
| if err := level.UnmarshalText([]byte(strings.ToLower(logLevel))); err != nil { | |
| level = slog.LevelInfo | |
| } | |
| } | |
| options := &slog.HandlerOptions{Level: level} | |
| var handler slog.Handler | |
| switch { | |
| case logFormat == "", strings.EqualFold(logFormat, "text"): | |
| handler = slog.NewTextHandler(os.Stdout, options) | |
| case strings.EqualFold(logFormat, "json"): | |
| handler = slog.NewJSONHandler(os.Stdout, options) | |
| default: | |
| handler = slog.NewTextHandler(os.Stdout, options) | |
| } | |
| slog.SetDefault(slog.New(handler)) | |
| } | |
| ``` | |
| ### Gitignore | |
| ``` | |
| # Binaries | |
| /bin | |
| *.exe | |
| *.dll | |
| *.so | |
| *.dylib | |
| # Environment files | |
| .env | |
| .env.* | |
| # Build | |
| /build | |
| /dist | |
| # IDE | |
| .vscode/ | |
| .idea/ | |
| # OS | |
| .DS_Store | |
| # Other | |
| /drafts | |
| /data | |
| ``` | |
| ### Dockerfile | |
| ```dockerfile | |
| FROM golang:1.24.4 AS builder | |
| WORKDIR /src | |
| ARG GIT_TAG | |
| ARG GIT_SHA | |
| ARG BUILD_DATE | |
| COPY go.mod go.sum ./ | |
| RUN go mod download | |
| COPY . . | |
| RUN CGO_ENABLED=0 GOOS=linux make build GIT_TAG="${GIT_TAG}" GIT_SHA="${GIT_SHA}" BUILD_DATE="${BUILD_DATE}" | |
| FROM scratch | |
| COPY --from=builder /src/bin/appname /appname | |
| ENTRYPOINT ["/appname"] | |
| ``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment