Skip to content

Weekly Review — Month 4 · Week 3 (Days 099–105)

Journal index · Roadmap › this week

📅 The Week in One Line

The production layer between the router and the database: validating requests, logging every request, paginating results, configuring from the environment, rate limiting, and authenticating with JWT + role-based access control.

✅ What I Completed

  • Day 099 — Request validation: decode-then-validate, collect all FieldErrors, 400 vs 422, net/mail.ParseAddress
  • Day 100 — Per-request structured logging: log/slog, JSON handler, ResponseWriter wrapper for status, request-id in context
  • Day 101 — Pagination & filtering: clamp limit/offset, bounds-safe slicing, offset vs cursor/keyset, allow-list filters
  • Day 102 — Config from env: os.LookupEnv, typed parsing, required vars, fail-fast with errors.Join, injected lookup
  • Day 103 — Rate limiting: token bucket (rate vs burst), lazy refill under a mutex, 429 + Retry-After, idle-bucket eviction
  • Day 104 — JWT auth & RBAC: HS256 sign/verify, base64.RawURLEncoding, alg-pinning, hmac.Equal, exp, 401 vs 403
  • Day 105 — Review + closed-book recall
  • Examples: validation, reqlog, ratelimit, jwt (stdlib-only, runnable)
  • Exercises solved: 3 (validate, paginate, envconfig)

💡 Lessons Learned

  • Parsing and validation are different questions with different codes: can't decode → 400; decodes but breaks a rule → 422. Collect every field error into a map for one stable, parseable response.
  • log/slog is stdlib now (1.21+). Wrap ResponseWriter to capture status (default it to 200!), log on defer so status/duration are final, and put the request id in context under an unexported key type.
  • Query params are hostile: coerce and clamp limit/offset (always cap limit) rather than 400-ing; slice with min-clamped bounds so an offset past the end is an empty page, not a panic.
  • os.LookupEnv distinguishes unset from set-empty; parse config once at boot into a typed struct, inject the lookup for testability, and errors.Join every problem so a bad deploy reports them all at once.
  • Token bucket = burst (capacity) + rate (refill). Refill lazily on access under a mutex; map writes aren't concurrency-safe; evict idle buckets; answer 429 + Retry-After.
  • Verify a JWT before trusting it: pin alg, recompute the HMAC, compare with hmac.Equal (constant-time), check exp — then read claims. AuthN failure is 401; AuthZ (wrong role) is 403.

💪 Strengths (what clicked)

  • The status-code taxonomy (400 / 401 / 403 / 422 / 429) finally feels precise rather than "4xx-ish".
  • Middleware composition: each cross-cutting concern (log, limit, auth) is a func(http.Handler) http.Handler I can chain.
  • Dependency injection for testability — injecting now into Verify and a LookupFunc into Load made both pure and table-testable.

🧩 Weaknesses (what's still fuzzy)

  • Encoding/signing opaque cursors for keyset pagination and surfacing them (next_cursor vs Link header).
  • Distributed rate limiting — the in-memory map only limits per process.
  • JWT lifecycle: refresh tokens, rotation, and revocation before exp.

🔁 Spaced-Repetition Re-quiz (topics from earlier weeks)

  1. Q: (Wk1) What does Go 1.22's http.ServeMux give you over the old mux?

    A Method-aware patterns (`GET /items/{id}`), path wildcards `{id}` / `{path...}` read via `r.PathValue`, the `{$}` exact-match anchor, and automatic `405 Method Not Allowed`.

  2. Q: (Wk2) Why doesn't sql.Open connect, and what do you call to verify?

    A `Open` only validates arguments and prepares the pool lazily; no connection is made until first use. Call `db.PingContext(ctx)` at startup to verify connectivity.

  3. Q: (Wk2) One-line rule for safe transactions in Go?

    A `defer tx.Rollback()` right after `BeginTx` (it no-ops after a successful `Commit`), run statements on `tx`, propagate `ctx`, and check `Commit`'s error.

  4. Q: (Wk3) Which status for: malformed JSON · rule violation · over the limit · missing token · wrong role?

    A 400 · 422 · 429 · 401 · 403.

🎯 Action Items

  • Add an opaque, signed cursor to the paginate exercise and expose next_cursor in a JSON envelope.
  • Compose the week's middlewares (request-id → slog → ratelimit → JWT) into a single Chain and unit-test the order with httptest.
  • Read golang.org/x/time/rate source to compare with the hand-rolled bucket.
  • Sketch a refresh-token flow (short access JWT + long refresh) and where revocation state would live.

🚀 Next Week Goals

  • Tie the service together: end-to-end tests of the HTTP + DB stack with httptest and an in-memory repository.
  • Observability beyond logs: basic metrics and request tracing.
  • Package the service for deploy (config, health checks, graceful shutdown).

📊 Metrics

Hours Days hit Exercises Commits Avg confidence
9 7/7 3 7 3.⅘

Suggested commit: docs(journal): month 4 week 3 review