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
Running with Docker Compose (recommended)
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
- Go to the Google Cloud Console credentials page.
- Create an OAuth 2.0 Client ID (Application type: Web application).
- Add an Authorized redirect URI:
http://localhost:8080/auth/google/callback(must matchGOOGLE_REDIRECT_URLin your.envexactly). - 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) andSameSite=Lax(mitigates CSRF);Secureis enabled automatically whenENV=production. - The OAuth2 flow uses a random
statevalue, checked on callback, to prevent CSRF against the login flow itself. /loginand/registerhave 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 ofCREATE 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/loggingandinternal/middleware/request_logger.go)