Essential Libraries Basics
10 examples to get you started with Essential Libraries - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Create a module:
mkdir libdemo && cd libdemo && go mod init example.com/libdemo. - Add common libraries:
go get go.uber.org/zap github.com/stretchr/testify/require github.com/spf13/viper github.com/go-chi/chi/v5. - Run examples with
go run .after saving asmain.go(or split files in the same module).
Basic Examples
1. zap production logger
Structured JSON logs with minimal setup.
package main
import (
"go.uber.org/zap"
)
func main() {
log, _ := zap.NewProduction()
defer log.Sync()
log.Info("service ready", zap.String("port", "8080"))
}NewProductionsets JSON output, sampling, and sensible defaults.defer log.Sync()flushes buffers on shutdown (ignore errors on some platforms).- Prefer injecting
*zap.Loggerinto constructors instead of global state.
Related: Logging: zap vs zerolog vs slog - when to pick zap over slog
2. slog JSON handler (stdlib alternative)
No third-party logger required for structured fields.
package main
import (
"log/slog"
"os"
)
func main() {
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
slog.SetDefault(slog.New(h))
slog.Info("ready", "service", "api")
}- Go 1.21+ ships
log/slogin stdlib. - Key-value pairs follow the message argument.
- Many teams standardize on slog and keep zap only for latency-critical paths.
Related: Structured Logging with slog - handlers and migration
3. testify require assertion
Readable test failures without custom helpers.
package user_test
import (
"testing"
"github.com/stretchr/testify/require"
)
func sum(a, b int) int { return a + b }
func TestSum(t *testing.T) {
require.Equal(t, 5, sum(2, 3))
}requirestops the test on failure (use in setup preconditions).- Save this in
user_test.go; testify is test-only, not production code. - Pair with table-driven
testsslices for coverage.
Related: Testing with testify & mockery - suites and mocks
4. viper read config file
Load YAML defaults with env override support.
package main
import (
"fmt"
"github.com/spf13/viper"
)
func main() {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")
_ = viper.ReadInConfig()
viper.SetDefault("port", 8080)
fmt.Println(viper.GetInt("port"))
}SetDefaultapplies when the key is missing from all sources.- Call
viper.AutomaticEnv()to letPORToverrideportkeys. - Keep config structs typed; viper is for loading, not business logic.
Related: Configuration with viper & envconfig - layered precedence
5. chi router with one route
Stdlib-compatible HTTP routing.
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
)
func main() {
r := chi.NewRouter()
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
http.ListenAndServe(":8080", r)
}chi.NewRouter()returns anhttp.Handler.- Handlers use standard
ResponseWriterandRequestsignatures. - Add middleware with
r.Usebefore registering routes.
Related: chi: Lightweight Routing & Middleware - route groups
6. chi URL parameter
Named path segments without framework context types.
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Write([]byte("user " + id))
}){id}captures one segment; validate before database lookups.- Empty string means the param was not set on this route pattern.
- Document param names in OpenAPI or route tables.
7. viper bind environment variable
Map DATABASE_URL into a typed config field.
viper.SetEnvPrefix("APP")
viper.BindEnv("database_url")
viper.AutomaticEnv()
dsn := viper.GetString("database_url")
_ = dsnSetEnvPrefixnamespaces env keys (APP_DATABASE_URL).- Bind explicit keys when automatic mapping is ambiguous.
- Fail fast in
mainwhen required secrets are empty.
Intermediate Examples
8. zap sugared logger for printf-style
SugaredLogger trades a little performance for ergonomics.
log, _ := zap.NewDevelopment()
defer log.Sync()
sugar := log.Sugar()
sugar.Infof("listening on %s", ":8080")- Prefer structured
log.Info("listening", zap.String("addr", addr))in hot paths. - Development config uses console encoding for local readability.
- Inject
*zap.Loggerand call.Sugar()only at legacy boundaries.
9. testify suite skeleton
Group related tests with shared setup hooks.
import (
"testing"
"github.com/stretchr/testify/suite"
)
type UserSuite struct{ suite.Suite }
func (s *UserSuite) TestCreate() {
s.Equal(1, 1)
}
func TestUserSuite(t *testing.T) {
suite.Run(t, new(UserSuite))
}SetupTest/TearDownTestrun around each test method.- Suites help HTTP integration tests with shared
httptestservers. - Do not replace table-driven unit tests where suites add ceremony.
Related: testify, httptest & Test Doubles - doubles and httptest
10. chi middleware plus zap request log
Compose focused libraries at the HTTP edge.
import (
"net/http"
"time"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
func requestLog(log *zap.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
next.ServeHTTP(ww, r)
log.Info("request",
zap.String("method", r.Method),
zap.String("path", r.URL.Path),
zap.Int("status", ww.Status()),
zap.Duration("latency", time.Since(start)),
)
})
}
}- chi's
WrapResponseWritercaptures status codes for access logs. - Keep middleware outermost: recovery, logging, then auth.
- Pass
r.Context()into downstream calls for cancellation.
Related: Prometheus & OpenTelemetry Client Libraries - metrics alongside logs
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).