Web Server: Skills Applied Together
This example shows how Go skills integrate in a real HTTP server. Each section references the relevant skill for detailed guidance.
Structure
package main
import (
"context"
"encoding/json"
"errors"
"log/slog"
"net/http"
"os"
"os/signal"
"time"
)
// --- Interfaces (go-interfaces) ---
// Store defines the data access boundary. Defined in the consumer
// package, not the implementation package.
type Store interface {
GetUser(ctx context.Context, id string) (*User, error)
}
// --- Types and constructors (go-naming, go-declarations) ---
// Server handles HTTP requests for the user API.
type Server struct {
store Store
router *http.ServeMux
}
// NewServer creates a Server with the given dependencies.
// The caller must call Shutdown to release resources.
func NewServer(store Store) *Server {
s := &Server{store: store}
s.router = http.NewServeMux()
s.router.HandleFunc("GET /users/{id}", s.handleGetUser)
return s
}
// --- Error handling (go-error-handling) ---
// Domain errors as sentinels — checked with errors.Is.
var ErrNotFound = errors.New("not found")
// --- HTTP handler (go-control-flow, go-context, go-error-handling) ---
func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // go-context: derive from request
id := r.PathValue("id")
user, err := s.store.GetUser(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) { // go-error-handling: errors.Is
http.Error(w, "user not found", http.StatusNotFound)
return // go-control-flow: early return
}
// HTTP handlers are an exception to "log OR return": log detail server-side, return sanitized error to client.
slog.Error("GetUser failed", "id", id, "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
// --- Graceful shutdown (go-concurrency, go-defensive) ---
func main() {
store := NewDBStore(os.Getenv("DATABASE_URL"))
srv := NewServer(store)
httpSrv := &http.Server{
Addr: ":8080",
Handler: srv.router,
ReadTimeout: 5 * time.Second, // go-defensive: use time.Duration
WriteTimeout: 10 * time.Second,
}
// go-concurrency: goroutine lifetime is clear
go func() {
sigCh := make(chan os.Signal, 1) // go-concurrency: channel size 1
signal.Notify(sigCh, os.Interrupt)
<-sigCh
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // go-defensive: defer cleanup
if err := httpSrv.Shutdown(ctx); err != nil {
slog.Error("server shutdown failed", "err", err)
}
}()
slog.Info("starting server", "addr", httpSrv.Addr)
if err := httpSrv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
slog.Error("server error", "err", err)
os.Exit(1) // go-packages: exit only from main
}
}Skills Applied
| Area | Skill | What's demonstrated |
|---|---|---|
| Interface at consumer | go-interfaces | Store defined where it's used |
| Naming | go-naming | MixedCaps, receiver abbreviation, clear func names |
| Error handling | go-error-handling | Sentinels, errors.Is, log-or-return |
| Context | go-context | Derived from request, passed through |
| Control flow | go-control-flow | Early returns for error cases |
| Concurrency | go-concurrency | Clear goroutine lifetime, channel sizing |
| Defensive | go-defensive | defer cancel(), time.Duration, graceful shutdown |
| Packages | go-packages | Exit only in main() |
| Logging | go-logging | Structured slog, handle error once |