Go - Sample REST API
          Beginner 10/10
          Teacher 10/10
          Architect 10/10
        
        
        
        
Overview
This capstone brings together HTTP routing, JSON encoding/decoding, context, logging, and a clean project layout. You can paste each file into your editor and run the service locally.
Project Layout
app/
  cmd/api/main.go
  internal/httpx/handlers.go
  go.mod
go.mod
module example.com/app
go 1.22.0
require (
)
cmd/api/main.go
package main
import (
  "log"
  "net/http"
  "os"
  "time"
  "example.com/app/internal/httpx"
)
func main(){
  mux := http.NewServeMux()
  mux.HandleFunc("/health", httpx.Health)
  mux.HandleFunc("/echo", httpx.Echo)
  srv := &http.Server{
    Addr:              ":8080",
    Handler:           logRequests(mux),
    ReadHeaderTimeout: 5 * time.Second,
  }
  log.Println("listening on", srv.Addr)
  if err := srv.ListenAndServe(); err != nil {
    log.Println("server error:", err)
    os.Exit(1)
  }
}
func logRequests(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
    start := time.Now()
    next.ServeHTTP(w, r)
    log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
  })
}
internal/httpx/handlers.go
package httpx
import (
  "encoding/json"
  "net/http"
  "time"
  "strings"
)
type Health struct{ Status string `json:"status"`; Time time.Time `json:"time"` }
type EchoReq struct{ Message string `json:"message"` }
type EchoResp struct{ Upper string `json:"upper"` }
func Health(w http.ResponseWriter, r *http.Request){
  w.Header().Set("Content-Type","application/json")
  _ = json.NewEncoder(w).Encode(Health{Status:"ok", Time: time.Now()})
}
func Echo(w http.ResponseWriter, r *http.Request){
  if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed); return }
  var req EchoReq
  if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    http.Error(w, "bad json", http.StatusBadRequest); return
  }
  resp := EchoResp{ Upper: strings.ToUpper(req.Message) }
  w.Header().Set("Content-Type","application/json")
  _ = json.NewEncoder(w).Encode(resp)
}
Run
go run ./cmd/api
# in another terminal
curl -s localhost:8080/health | jq
curl -s -X POST localhost:8080/echo -d '{"message":"hello"}' -H 'Content-Type: application/json' | jq
Exercises
- Add request ID middleware. Return the ID in responses.
- Validate Echo input: reject empty messages with 422.
- Add /time endpoint that supports ?zone=UTC or local time.
Testing and Linting
go test ./...
go vet ./...
# optional if installed
golangci-lint run
Makefile (optional)
build:
	go build -o bin/api ./cmd/api
test:
	go test ./...
lint:
	go vet ./...
	golangci-lint run || true
run:
	go run ./cmd/api