Go
This curriculum takes you from advanced beginner to intermediate Go developer through five practical projects. Each project builds on the last and introduces patterns you’ll use in production systems.
Project 1: ghstat, GitHub repository CLI
Difficulty: 3 / 5
What you build
ghstat is a CLI tool that fetches and displays statistics for one or more GitHub repositories: stars over time, top contributors, and open issues by label. A local cache prevents redundant API calls on repeated runs.
Requirements
- Implement subcommands (
ghstat repo stats,ghstat repo compare) using Cobra. - Configure the HTTP client (timeout, retry count, auth token) using the functional options pattern.
- Fetch multiple repositories concurrently using goroutines and
errgroup. - Cache responses to disk as JSON. Invalidate the cache after a configurable TTL.
- Wrap all errors with context:
fmt.Errorf("fetching repo %s: %w", name, err).
What you learn
- Cobra subcommand structure and flag binding
- Functional options pattern for configuring structs
- Concurrent fan-out with
errgroup - JSON serialization and file I/O for caching
- Error wrapping with
%w
Why it matters
CLIs are the fastest path to shipping something real in Go. This project forces a clean package boundary between a github client package and a cmd package. The fan-out pattern you implement here reappears directly in Projects 3 and 5.
Project 2: Webhook ingestion service
Difficulty: 3.5 / 5
What you build
You’ll build an HTTP service that receives, validates, and stores inbound webhooks from any HMAC-signed provider (GitHub, Stripe, and similar). The service exposes three endpoints:
POST /hooks/:provider: receive and store an eventGET /events: list stored events, filterable by provider and statusPOST /events/:id/replay: re-deliver a stored event to a configurable target URL
Requirements
- Build the server using
net/httpdirectly. Do not use a router framework. - Implement HMAC-SHA256 signature verification as a
func(http.Handler) http.Handlermiddleware. Read and buffer the request body once. Pass it to downstream handlers without consuming it twice. - Define a
Storeinterface withSave,List,Get, andMarkReplayedmethods. Implement it as an in-memory struct guarded by async.RWMutex. - Inject
http.Clientas a dependency for the replay handler. Do not usehttp.DefaultClient. - Implement graceful shutdown: listen for
os.Signal, callserver.Shutdown(ctx)with a timeout, and drain in-flight replay requests before exiting. - Write table-driven tests using
httptest.NewRecorder. Include cases that send invalid signatures and confirm rejection.
What you learn
net/httphandler and middleware composition- HMAC signature verification, the standard webhook security pattern
- Interface-driven storage design
- Graceful shutdown with
context.WithTimeout - Testing HTTP handlers with
httptest
Why it matters
Webhook handling appears in nearly every backend integration. Stripe, GitHub, Twilio, and most SaaS platforms use the same HMAC pattern. Injecting http.Client as a dependency rather than using http.DefaultClient is a small habit with large consequences for testability. The Store interface you define here is the same pattern you’ll use in Project 4 with a real database.
Project 3: logwatch, concurrent log file analyzer
Difficulty: 4 / 5
What you build
logwatch ingests one or more log files concurrently, parses lines against configurable patterns (error rate, slow requests above a threshold, IP frequency), and emits a structured summary report to stdout or as JSON.
Requirements
- Implement a worker pool: a fixed number of goroutines consume from a shared
chan stringof lines. Do not spawn one goroutine per line. - Structure processing as a pipeline with three stages:
producer → parser → aggregator. Connect each stage with a channel. - Propagate a
context.Contextthrough every stage. When the user cancels (Ctrl-C), all stages must drain and exit cleanly. No goroutine leaks. - Use
sync.Poolto reuse line buffers and reduce garbage collection pressure on large files. - Read files with
bufio.Scanner. - Emit structured output using
log/slog(Go 1.21+).
What you learn
- Worker pool pattern with bounded concurrency
- Pipeline pattern: staged processing over channels
- Context cancellation and goroutine lifecycle management
sync.Poolfor buffer reuse- Structured logging with
slog
Why it matters
After completing this project, you’ll have implemented the two concurrency patterns that appear most often in real Go systems: worker pools and pipelines. You’ll find both in data ingestion systems, ETL pipelines, stream processors, and CI runners. Context cancellation is required knowledge for any production Go codebase. Every blocking operation must respect it.
Project 4: Task management API with PostgreSQL
Difficulty: 4 / 5
What you build
You’ll build a REST API for managing tasks, users, and labels, backed by PostgreSQL. Users authenticate with JWT. All data operations run against a real database with versioned migrations.
Requirements
- Use
pgxwithpgxpoolfor connection pooling. Set the pool size explicitly. Do not rely on defaults. - Write database migrations with
golang-migrate. Run migrations at startup. - Implement transactions for multi-step operations (for example, creating a task and assigning labels atomically).
- Define custom error types:
ErrNotFound,ErrConflict, andErrUnauthorized. Map them to HTTP status codes in a single handler helper. No type switch per handler. - Implement JWT authentication as middleware, building on the middleware chain from Project 2.
- Write integration tests using
testcontainers-go: spin up a real PostgreSQL container per test run. Do not mock the database layer.
What you learn
pgxandpgxpoolfor production database access- Schema versioning with
golang-migrate - Transaction handling for atomic multi-step writes
- Custom error types that carry behavior, not just messages
- Integration testing with real infrastructure via
testcontainers-go - JWT middleware layered on an existing middleware chain
Why it matters
Most production Go services talk to a database. Understanding pgxpool prevents connection exhaustion, one of the most common production incidents in Go. Integration tests with testcontainers-go catch real bugs that mock-based tests hide. Custom error types let you handle failures consistently across handlers without duplicating logic.
Project 5: Reverse proxy with health checks and circuit breaking
Difficulty: 4.5 / 5
What you build
You’ll build an HTTP reverse proxy that load-balances across a configurable list of backend addresses. Background health checks remove unhealthy backends from rotation. A per-backend circuit breaker stops forwarding to failing backends and retries them after a cooldown period. The proxy exposes Prometheus metrics on /metrics.
Requirements
- Use
httputil.ReverseProxy. Customize it with aDirectorfunction and aModifyResponsehook. - Run background health checkers on a
time.Ticker. Each checker must respect context cancellation and exit cleanly when the proxy shuts down. - Implement a circuit breaker state machine (closed → open → half-open) as a struct guarded by
sync.Mutex. Do not use a third-party circuit breaker library. - Use
sync/atomicfor high-frequency counters (in-flight requests, failure counts) to avoid lock contention. - Instrument the proxy with the Prometheus Go client: expose in-flight request counts, request duration histograms, and a backend health gauge.
- Configure backends, timeouts, and circuit breaker thresholds through functional options.
What you learn
httputil.ReverseProxycustomization- Background goroutine lifecycle management with context
- Circuit breaker state machine design
sync/atomicfor low-contention counters- Prometheus instrumentation: counters, histograms, gauges
- Functional options for complex component configuration
Why it matters
A proxy concentrates the hardest production Go problems in one place: concurrent state mutation, goroutine lifecycle, and observability. The circuit breaker is a foundational distributed systems pattern used in virtually every service-to-service call at scale. After this project, you can read and contribute to production codebases like Traefik, Caddy, or internal platform tooling.