Frontend Labs
⋯

14. Terminal project: a JSON config server

Goal: a small net/http service — the Week-1 mini-project from the 90-day plan, built out fully here as a reference implementation you can diff your own attempt against.

// config/config.go
package config

import "sync"

type Store struct {
	mu   sync.RWMutex
	data map[string]string
}

func New() *Store { return &Store{data: map[string]string{}} }

func (s *Store) All() map[string]string {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make(map[string]string, len(s.data))
	for k, v := range s.data {
		out[k] = v
	}
	return out
}

func (s *Store) Set(key, value string) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.data[key] = value
}
// server/server.go
package server

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"example.com/configserver/config"
)

type Server struct {
	store     *config.Store
	startedAt time.Time
}

func New(store *config.Store) *Server {
	return &Server{store: store, startedAt: time.Now()}
}

func (s *Server) Routes() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /config", s.handleGetAll)
	mux.HandleFunc("PUT /config/{key}", s.handleSet)
	mux.HandleFunc("GET /health", s.handleHealth)
	return mux
}

func (s *Server) handleGetAll(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, s.store.All())
}

func (s *Server) handleSet(w http.ResponseWriter, r *http.Request) {
	key := r.PathValue("key")
	if strings.TrimSpace(key) == "" {
		http.Error(w, "key required", http.StatusBadRequest)
		return
	}
	var body struct{ Value string `json:"value"` }
	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
		http.Error(w, "invalid body", http.StatusBadRequest)
		return
	}
	s.store.Set(key, body.Value)
	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]any{
		"status": "ok",
		"uptime": time.Since(s.startedAt).String(),
	})
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(v); err != nil {
		return
	}
}
// main.go
package main

import (
	"log"
	"net/http"

	"example.com/configserver/config"
	"example.com/configserver/server"
)

func main() {
	store := config.New()
	srv := server.New(store)
	log.Println("listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", srv.Routes()))
}
curl -X PUT localhost:8080/config/feature_x -d '{"value":"on"}'
curl localhost:8080/config
curl localhost:8080/health

Notice the shape: config knows nothing about HTTP, server knows nothing about how config is stored — swap config.Store for a Redis-backed implementation later and server doesn't change. That separation is worth more than any framework.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36