Files
go-simple-api/README.md
T
2026-07-16 10:13:46 +03:30

6.5 KiB

go-simple-api

A small, heavily-commented Go REST API built as a learning project. It implements user authentication two ways - email/password and "Sign in with Google" - using server-side sessions stored in Redis, backed by MySQL for user data.

This project was built incrementally, lesson by lesson, specifically to teach Go web-service fundamentals. Every file has generous inline comments explaining why the code is written the way it is, not just what it does. See docs/LESSONS.md for the full course this project was built from, and docs/ARCHITECTURE.md for a deeper explanation of how the pieces fit together.

Stack

Concern Technology
HTTP routing chi v5
Structured logging log/slog (Go standard library), JSON output
Database MySQL, via database/sql + go-sql-driver/mysql
Sessions scs v2, backed by Redis
Password hashing golang.org/x/crypto/bcrypt
Google login golang.org/x/oauth2 (Authorization Code flow)
Rate limiting httprate
CORS go-chi/cors

Project layout

go-simple-api/
├── cmd/api/main.go              # entrypoint: wires everything, runs the server
├── internal/
│   ├── config/                  # env var loading
│   ├── logging/                 # structured JSON logger (slog)
│   ├── database/                # MySQL connection + migrations
│   ├── models/                  # User struct + UserRepository (all SQL lives here)
│   ├── session/                 # scs session manager, backed by Redis
│   ├── oauth/                   # Google oauth2.Config builder
│   ├── handlers/                # HTTP handlers (health, auth, google oauth)
│   ├── middleware/               # request logging + auth-guard middleware
│   └── router/                  # wires routes + middleware together
├── docs/                        # architecture, API reference, course notes
├── Dockerfile                   # multi-stage build
├── docker-compose.yml           # app + MySQL + Redis
├── .env.example                 # every config variable, documented
└── go.mod

Running locally (without Docker)

Requires Go 1.26+, and MySQL + Redis reachable somewhere.

# 1. Start MySQL and Redis (or point at existing instances)
docker run --name mysql-api -e MYSQL_ROOT_PASSWORD=devpass \
  -e MYSQL_DATABASE=go_simple_api -p 3306:3306 -d mysql:9
docker run --name redis-api -p 6379:6379 -d redis:8

# 2. Set up environment
cp .env.example .env
# edit .env - at minimum, fill in GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
# if you want to test Google login. Password login works without them.
export $(grep -v '^#' .env | xargs)   # or use a tool like direnv

# 3. Fetch dependencies (go.sum is not included - see go.mod for why)
go mod tidy

# 4. Run
go run ./cmd/api

The server listens on :8080 by default. Try:

curl http://localhost:8080/health
cp .env.example .env
# fill in GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET if you want Google login

docker compose up --build

This starts the API, MySQL, and Redis together, with the API waiting on the other two. See docs/ARCHITECTURE.md for how service networking works inside Compose.

Stop everything:

docker compose down        # stops containers, keeps the MySQL volume
docker compose down -v     # also wipes the MySQL volume (fresh start)

API reference

See docs/API.md for every endpoint, request/response shapes, and example curl commands.

Quick overview:

Method Path Auth required? Purpose
GET /health no Liveness check
POST /register no Create a password-based account
POST /login no Log in with email + password, starts a session
POST /logout no (needs a session to destroy) Ends the current session
GET /me yes Returns the currently logged-in user
GET /auth/google/login no Redirects the browser to Google
GET /auth/google/callback no Google redirects here after login

Google OAuth setup

  1. Go to the Google Cloud Console credentials page.
  2. Create an OAuth 2.0 Client ID (Application type: Web application).
  3. Add an Authorized redirect URI: http://localhost:8080/auth/google/callback (must match GOOGLE_REDIRECT_URL in your .env exactly).
  4. Copy the Client ID and Client Secret into .env.

Security notes

This project deliberately implements several production-appropriate security practices, explained in detail in the code comments where they appear:

  • Passwords are hashed with bcrypt, never stored or logged in plaintext.
  • Login returns an identical, generic error for both "no such email" and "wrong password", to avoid leaking which emails are registered.
  • Sessions are server-side (Redis-backed) - the browser only ever holds a random token, never the actual session data.
  • sessions.RenewToken() is called on every successful login (password or Google) to prevent session fixation.
  • The session cookie is HttpOnly (JS can't read it) and SameSite=Lax (mitigates CSRF); Secure is enabled automatically when ENV=production.
  • The OAuth2 flow uses a random state value, checked on callback, to prevent CSRF against the login flow itself.
  • /login and /register have a much stricter rate limit than the rest of the API, to slow down credential-stuffing / brute-force attempts.
  • CORS is an explicit origin allowlist (ALLOWED_ORIGINS), never a wildcard, since the API uses credentialed (cookie-based) requests.

What's not included (possible next steps)

  • Automated tests (httptest, table-driven tests, a mockable repository interface)
  • A real migration tool (e.g. golang-migrate) instead of CREATE TABLE IF NOT EXISTS
  • CSRF tokens for a same-origin HTML form frontend (SameSite=Lax already covers the cookie-based JSON API case)
  • Refresh/renewal flow for sessions beyond their fixed 24h lifetime
  • Machine-readable error codes in API responses (currently just a message string)
  • Shipping logs to Grafana Loki via Grafana Alloy (the JSON log shape produced by this app is already Alloy/Loki-friendly - see internal/logging and internal/middleware/request_logger.go)