Weekly Review — Month 4 · Week 3 (Days 099–105)¶
📅 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,ResponseWriterwrapper for status, request-id incontext - 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 witherrors.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/slogis stdlib now (1.21+). WrapResponseWriterto capture status (default it to 200!), log ondeferso status/duration are final, and put the request id incontextunder an unexported key type.- Query params are hostile: coerce and clamp
limit/offset(always caplimit) rather than 400-ing; slice withmin-clamped bounds so an offset past the end is an empty page, not a panic. os.LookupEnvdistinguishes unset from set-empty; parse config once at boot into a typed struct, inject the lookup for testability, anderrors.Joinevery 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 withhmac.Equal(constant-time), checkexp— 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.HandlerI can chain. - Dependency injection for testability — injecting
nowintoVerifyand aLookupFuncintoLoadmade both pure and table-testable.
🧩 Weaknesses (what's still fuzzy)¶
- Encoding/signing opaque cursors for keyset pagination and surfacing them
(
next_cursorvsLinkheader). - 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)¶
-
Q: (Wk1) What does Go 1.22's
http.ServeMuxgive 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`. -
Q: (Wk2) Why doesn't
sql.Openconnect, 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. -
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. -
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_cursorin a JSON envelope. - Compose the week's middlewares (request-id → slog → ratelimit → JWT) into a
single
Chainand unit-test the order withhttptest. - Read
golang.org/x/time/ratesource 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
httptestand 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