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