Files
2026-07-16 10:13:46 +03:30

5.1 KiB

The course this project was built from

This project was built incrementally across 10 lessons, each adding one concept on top of the last. This file is a map from "concept" to "where it lives in the code" - useful if you want to revisit how/why something was built the way it was.

# Lesson New concepts Where it lives
1 Project skeleton & chi routing Standard Go project layout, chi.Mux, middleware basics, graceful shutdown via http.Server + srv.Shutdown() cmd/api/main.go, internal/router/router.go, internal/handlers/health.go
2 Structured JSON logging log/slog, slog.NewJSONHandler, log levels, the three-layer middleware-factory pattern internal/logging/logger.go, internal/middleware/request_logger.go
3 Config & MySQL connection database/sql, connection pooling (SetMaxOpenConns etc.), DSNs, context.WithTimeout for a hard deadline on the initial ping internal/config/config.go, internal/database/mysql.go
4 User model & repository pattern Pointers (*/&) in depth, pointer receivers, the repository pattern, sentinel errors + errors.Is internal/models/user.go, internal/models/user_repository.go
5 Password login bcrypt hashing/salting, decoding JSON request bodies, struct tags, generic error messages to avoid user enumeration internal/handlers/auth.go (Register/Login), internal/handlers/respond.go
6 Server-side sessions (scs + Redis) scs.SessionManager, swapping storage backends via .Store, cookie flags (HttpOnly, SameSite), RenewToken to prevent session fixation internal/session/session.go, internal/session/keys.go, Login/Logout/Me in internal/handlers/auth.go
7 Login with Google (OAuth2) Authorization Code flow, oauth2.Config, CSRF state parameter, account linking by email internal/oauth/google.go, internal/handlers/oauth_google.go
8 Auth middleware & route protection context.Context (WithValue/Value), private context-key types, type assertions, chi route groups internal/middleware/require_auth.go, r.Group(...) in internal/router/router.go
9 Rate limiting & security hardening httprate.LimitByIP, CORS (go-chi/cors), environment-aware Secure cookie flag internal/router/router.go, internal/session/session.go, internal/config/config.go
10 Docker & wrap-up Multi-stage Docker builds, docker-compose, service-name-as-hostname networking, named volumes Dockerfile, docker-compose.yml

Core Go ideas that recur throughout the codebase

These aren't tied to a single lesson - once introduced, they show up repeatedly, and are worth having solid:

  • Pointers (* / &) - sharing one instance of something stateful (*sql.DB, *scs.SessionManager, *slog.Logger) across the whole app instead of copying it; writing a result back into a caller's variable (rows.Scan(&x), u.ID = int(id) inside Create(ctx, u *User)).
  • Interfaces satisfied implicitly - *chi.Mux satisfies http.Handler just by having a ServeHTTP method; there's no implements keyword in Go.
  • Closures / the three-layer middleware pattern - seen in both RequestLogger(logger) and RequireAuth(sessions, userRepo, logger): an outer function captures dependencies, returns a func(http.Handler) http.Handler, which itself returns the actual per-request handler - three layers, each running at a different time.
  • context.Context - carrying request-scoped values (the current user, a request ID) and deadlines (timeouts) through a call chain without adding extra parameters to every function signature.
  • Error wrapping and sentinel errors - fmt.Errorf("...: %w", err) to add context while preserving the original error; var ErrUserNotFound = errors.New(...) plus errors.Is(err, ErrUserNotFound) to let callers branch on error kind without string-matching messages.
  • Dependency injection via structs - AuthHandler{userRepo, sessions, logger} instead of global variables, so every handler's requirements are explicit and visible in its constructor.

Suggested next steps

If you want to keep extending this project as further practice:

  1. Testing - httptest.NewRequest/NewRecorder for handler tests, table-driven test cases, and extracting a UserStore interface so UserRepository can be swapped for an in-memory fake in tests.
  2. A real migration tool (e.g. golang-migrate/migrate) instead of CREATE TABLE IF NOT EXISTS on every boot.
  3. CSRF tokens if you ever add a same-origin HTML form frontend (the current SameSite=Lax cookie already covers the JSON-API case).
  4. Refresh/renewal so an active user's session doesn't hard-expire after 24 hours regardless of activity.
  5. Machine-readable error codes ({"error_code": "invalid_credentials"}) so a frontend can branch on a stable code instead of parsing message text.
  6. Grafana Alloy + Loki - point Alloy at this container's stdout; the JSON shape from internal/logging and internal/middleware/request_logger.go is already structured for it.