Chapter 01
Go essentials for the backend
The parts of Go that shape every service: errors as values, interfaces, goroutines, context and modules.
Errors as values
Also called error wrapping, errors.Is, error value
- What
- Go functions return an
erroralongside the result, and callers check it explicitly.fmt.Errorfwith%wwraps an error with context;errors.Isanderrors.Asinspect the chain. - Why
- Every failure path is visible in the code, and wrapped errors read like a trail: "load user 42: connection refused".
user, err := store.User(ctx, id)
if errors.Is(err, pgx.ErrNoRows) {
return User{}, ErrNotFound
}
if err != nil {
return User{}, fmt.Errorf("load user %s: %w", id, err)
}Watch out
Ignoring errors with _, or logging and returning the same error so it is reported many times.
Structs and implicit interfaces
Also called interface, struct
- What
- A struct holds data and methods. A type satisfies an interface just by having its methods, with no
implementskeyword. - Why
- Small interfaces declared where they are used ("accept interfaces, return structs") make it easy to swap a Postgres store for a fake in tests.
type UserStore interface {
User(ctx context.Context, id string) (User, error)
}
type PGStore struct{ pool *pgxpool.Pool }
func (s *PGStore) User(ctx context.Context, id string) (
User, error) {
return queryUser(ctx, s.pool, id)
}Watch out
Defining large interfaces up front next to the implementation, Java style, before any second implementation exists.
Goroutines and channels
Also called goroutine, channel, errgroup
- What
- A goroutine is a function running concurrently, costing a few kilobytes of stack. Channels pass values between goroutines safely.
net/httpalready runs each request in its own goroutine. - Why
- Concurrency is cheap and built in: fan out calls, run background workers, and use
errgroupto wait for a group and stop on the first error.
results := make(chan string, len(urls))
for _, u := range urls {
go func() {
results <- fetchTitle(u)
}()
}
for range urls {
fmt.Println(<-results)
}Watch out
Goroutine leaks: a goroutine blocked forever on a channel nobody reads, or with no way to stop when the request ends.
context.Context
Also called context, ctx
- What
- A value passed as the first argument through a call chain that carries a deadline, a cancellation signal and request-scoped values such as a trace ID.
- Why
- When a client disconnects or a timeout fires, every database query and outbound call using that context stops, freeing resources.
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
var name string
err := pool.QueryRow(ctx,
"SELECT name FROM users WHERE id = $1", id).Scan(&name)Watch out
Using context.Background() inside handlers instead of r.Context(), so work continues after the client has gone.
Packages and Go modules
Also called go.mod, go.sum
- What
- A module is a versioned collection of packages defined by
go.mod;go.sumrecords checksums. Code lives in packages, one per directory, and capitalised names are exported. - Why
- Builds are reproducible with no extra tooling, and the whole workflow (build, test, format, vet) comes with the
gocommand.
go mod init github.com/you/shop
go get github.com/jackc/pgx/v5
go mod tidy
go test ./...
go vet ./...
go build -o bin/api ./cmd/apiWatch out
Putting everything under internal/utils or common packages instead of grouping code by what it does.