Backend development: Go

Sefism.

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 error alongside the result, and callers check it explicitly. fmt.Errorf with %w wraps an error with context; errors.Is and errors.As inspect the chain.
Why
Every failure path is visible in the code, and wrapped errors read like a trail: "load user 42: connection refused".
Go
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 implements keyword.
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.
Go
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/http already runs each request in its own goroutine.
Why
Concurrency is cheap and built in: fan out calls, run background workers, and use errgroup to wait for a group and stop on the first error.
Go
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.
Go
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.sum records 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 go command.
Shell
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/api

Watch out

Putting everything under internal/utils or common packages instead of grouping code by what it does.

01 · Go essentials for the backend1 / 10