# Lesson 1 — Project Skeleton & chi Routing > **New Go concepts in this lesson:** packages/imports/modules, structs, > pointers, interfaces (implicitly satisfied), goroutines. If any of those > feel shaky, review `00-go-basics-3-interfaces-errors-concurrency-packages.md` > first. ## What we're building By the end of this lesson you'll have a real, runnable HTTP server with: - A standard Go project layout you'll keep extending for the rest of the course - A router (using the `chi` library) that maps URLs to handler functions - A `/health` endpoint that returns JSON - A **graceful shutdown** sequence — the server finishes in-flight requests before exiting on Ctrl+C, instead of just dying mid-request ## Project structure (final shape, built up over the whole course) ``` go-simple-api/ ├── cmd/api/main.go ├── internal/ │ ├── config/config.go │ ├── handlers/health.go │ └── router/router.go ├── go.mod ``` (More folders get added lesson by lesson — this is just what exists after Lesson 1.) ## Setup ```bash mkdir go-simple-api && cd go-simple-api go mod init git.hamidsoltani.com/hamid/go-simple-api go get github.com/go-chi/chi/v5@latest ``` A quick word on that module path: `git.hamidsoltani.com/hamid/go-simple-api` isn't a real, fetchable URL — it's just a naming convention (commonly your Git host + username + project name). It becomes the prefix for every internal import in this project, e.g. `git.hamidsoltani.com/hamid/go-simple-api/internal/config`. If you're following along with your own project, use your own path here — just stay consistent with it everywhere. `go get github.com/go-chi/chi/v5@latest` downloads [chi](https://github.com/go-chi/chi), a small, popular HTTP router for Go. Why use a router library instead of the standard library's own `http.ServeMux`? chi gives us URL parameters (`/users/{id}`), route groups, and a large ecosystem of compatible middleware (rate limiting, CORS, request logging) that we'll use throughout this course — the standard library's router is fine for very simple cases but doesn't have these built in. ## `internal/config/config.go` ```go package config import "os" type Config struct { Port string } func Load() Config { return Config{ Port: getEnv("PORT", "8080"), } } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } ``` Line by line: - `package config` — its own package, so both `main.go` and any future file can import it and call `config.Load()`. - `type Config struct { Port string }` — a plain struct holding settings. We'll add many more fields to this over the course (database settings, Redis settings, OAuth settings...) — this is the ONE place all of the app's configuration lives. - `func Load() Config` — returns a `Config` **by value** (not a pointer) since it's small and, once built, nothing needs to mutate it in place. - `getEnv` is unexported (lowercase — see Go Basics Part 2 on capitalization) — nothing outside this file needs to call it directly. `os.Getenv(key)` reads an environment variable; if it's empty (unset), we return `fallback` instead. This is how you avoid hardcoding things like ports directly in your code. ## `internal/handlers/health.go` ```go package handlers import ( "encoding/json" "net/http" ) func Health(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{ "status": "ok", }) } ``` - Every HTTP handler in Go (with or without chi) has this exact function signature: `func(w http.ResponseWriter, r *http.Request)`. - `w http.ResponseWriter` is how you write the response back to the client — it's an interface (see Go Basics Part 3) with methods like `Write`, `WriteHeader`, and `Header()`. - `r *http.Request` is a pointer to a struct describing the incoming request — method, URL, headers, body, etc. - `w.Header().Set("Content-Type", "application/json")` — sets a response header. This **must** happen before `w.WriteHeader(...)` is called — once you write the status code, the headers are locked in and can't be changed afterward. - `w.WriteHeader(http.StatusOK)` — writes the HTTP status code (`200`). `http.StatusOK` is just a predefined constant equal to `200` — using the named constant instead of the raw number is more readable and less error-prone. - `json.NewEncoder(w).Encode(map[string]string{"status": "ok"})` — from Go Basics Part 3: creates a JSON encoder that writes directly to `w` (which is a stream, an `io.Writer`), then encodes our map as JSON and writes it out. `map[string]string` is a map with `string` keys and `string` values — see Go Basics Part 3 on maps. ## `internal/router/router.go` ```go package router import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "git.hamidsoltani.com/hamid/go-simple-api/internal/handlers" ) func New() *chi.Mux { r := chi.NewRouter() r.Use(middleware.RequestID) r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Get("/health", handlers.Health) return r } ``` - `chi.NewRouter()` returns a `*chi.Mux` — a pointer to chi's router type. `*chi.Mux` happens to have a `ServeHTTP(w, r)` method, which means it automatically satisfies the standard library's `http.Handler` interface (see Go Basics Part 3 on interfaces) — no explicit declaration needed, it "just works" because the method exists. - `r.Use(...)` registers **middleware**: a function that wraps every request passing through the router. Each of these has the shape `func(http.Handler) http.Handler` — takes the "next" handler in the chain, returns a new handler that does something extra before/after calling it. - `middleware.RequestID` — tags each request with a unique ID (useful later once we add structured logging, in Lesson 2). - `middleware.Logger` — chi's built-in logger; prints a line per request to your terminal (we'll replace this with our own structured JSON version in Lesson 2). - `middleware.Recoverer` — catches panics inside any handler and turns them into a `500` response, instead of crashing the entire server process over one bad request. - `r.Get("/health", handlers.Health)` — registers `handlers.Health` to run for `GET` requests to `/health`. Note we pass the function itself (`handlers.Health`), not a call to it (`handlers.Health()`) — chi will call it later, once per matching request. ## `cmd/api/main.go` ```go package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "git.hamidsoltani.com/hamid/go-simple-api/internal/config" "git.hamidsoltani.com/hamid/go-simple-api/internal/router" ) func main() { cfg := config.Load() r := router.New() srv := &http.Server{ Addr: ":" + cfg.Port, Handler: r, } go func() { log.Printf("server starting on port %s", cfg.Port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server error: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("shutting down gracefully...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("forced shutdown: %v", err) } log.Println("server stopped") } ``` This is the most concept-dense file in the lesson. Take it slowly: - `cfg := config.Load()` then `r := router.New()` — build our two pieces using the constructors we just wrote. - `srv := &http.Server{Addr: ":" + cfg.Port, Handler: r}` — instead of calling the simpler `http.ListenAndServe(addr, handler)` directly, we build an `*http.Server` struct ourselves (note the `&` — we want a pointer, since we're going to call methods on it later that need to operate on this exact instance). We do this specifically so we can call `.Shutdown()` on it further down — `http.ListenAndServe` alone gives you no way to stop it gracefully. - `go func() { ... }()` — **this is a goroutine** (Go Basics Part 3, section 5). `srv.ListenAndServe()` blocks forever, serving requests until the server stops. If we called it directly here (without `go`), the code below it — the part that waits for Ctrl+C — would never run; the program would just sit inside `ListenAndServe` permanently. Running it as a goroutine lets it serve requests in the background while `main()`'s primary execution moves on to the next lines. - `if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed` — `ListenAndServe` always returns a non-nil error when it stops (even on a normal, intentional shutdown) — `http.ErrServerClosed` specifically means "this was a deliberate `Shutdown()` call, not a real problem," so we only treat OTHER errors as fatal. - `quit := make(chan os.Signal, 1)` — a **channel**, Go's built-in mechanism for goroutines to communicate. We're using it here in its simplest form: as a way to "wait for a signal to arrive." (We don't go deeper into channels in this course — this is the only one you'll need.) - `signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)` — tells the Go runtime "when the OS sends this process a SIGINT (Ctrl+C) or SIGTERM (e.g. `docker stop`), send a value into the `quit` channel instead of just killing the process outright." - `<-quit` — this is the **receive** operation on a channel: it blocks (pauses) the current goroutine — here, `main()`'s own execution — until something arrives on `quit`. This is what actually keeps the program alive and waiting, instead of exiting immediately after starting the server goroutine. - `context.WithTimeout(context.Background(), 5*time.Second)` — builds a `context.Context` (we'll use these a lot more starting in Lesson 8) that automatically expires after 5 seconds. `context.Background()` is the standard "empty starting point" for building a new context. - `defer cancel()` — `defer` schedules a function call to run right before the surrounding function (`main`, here) returns, regardless of how it returns. `cancel` releases resources associated with the timeout context once we're done with it — always pair `WithTimeout`/`WithCancel` with a `defer cancel()` immediately after creating them. - `srv.Shutdown(ctx)` — tells the server to stop accepting new connections, and wait for existing in-flight requests to finish, up to the 5-second deadline in `ctx`. This is what "graceful" shutdown means: requests that were already in progress get to complete normally instead of being cut off mid-response. ## Try it ```bash go run ./cmd/api ``` In another terminal: ```bash curl http://localhost:8080/health ``` You should get back `{"status":"ok"}`. Now go back to the terminal running the server and press **Ctrl+C**. You should see: ``` shutting down gracefully... server stopped ``` instead of the process just vanishing instantly — that's the graceful shutdown sequence working. ## Common mistakes at this stage - **Forgetting the parentheses when calling a function**: writing `r := router.New` (assigns the function itself) instead of `r := router.New()` (calls it and gets the `*chi.Mux` back). The compiler error looks like: `cannot use r (variable of type func() *chi.Mux) as http.Handler value` — if you see that shape of error, check for a missing `()`. - **Forgetting `defer db.Close()` / `defer cancel()`** on things that need cleanup — not an issue yet in this lesson, but a habit to build now, since it appears constantly starting in Lesson 3. Once `/health` works and Ctrl+C shuts down cleanly, move on to Lesson 2 — structured JSON logging.