Frontend Labs
⋯

Week 1 Mini Project: Configuration HTTP Server

Build an HTTP server that serves and updates runtime config as JSON. This is the skeleton of every internal admin tool — health endpoints, feature flags, WAF rule management.

File Structure

configserver/
├── main.go
├── config/
│   └── config.go
├── storage/
│   └── storage.go
└── server/
    └── server.go

Full Implementation

// config/config.go
package config

import (
    "sync"
    "time"
)

type Config struct {
    mu       sync.RWMutex
    data     map[string]string
    startedAt time.Time
}

func New() *Config {
    return &Config{
        data:      make(map[string]string),
        startedAt: time.Now(),
    }
}

func (c *Config) Set(key, value string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.data[key] = value
}

func (c *Config) Get(key string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.data[key]
    return v, ok
}

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

func (c *Config) Uptime() time.Duration {
    return time.Since(c.startedAt)
}
// main.go
package main

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

    "github.com/yourname/configserver/config"
)

func main() {
    cfg := config.New()
    cfg.Set("max_connections", "1000")
    cfg.Set("rate_limit_rps", "100")
    cfg.Set("waf_mode", "detection")

    mux := http.NewServeMux()

    // GET /config — return all config as JSON
    mux.HandleFunc("GET /config", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(cfg.All())
    })

    // PUT /config/{key} — update a value
    mux.HandleFunc("PUT /config/", func(w http.ResponseWriter, r *http.Request) {
        key := strings.TrimPrefix(r.URL.Path, "/config/")
        if 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 JSON", http.StatusBadRequest)
            return
        }
        cfg.Set(key, body.Value)
        w.WriteHeader(http.StatusNoContent)
    })

    // GET /health — liveness + uptime
    mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        json.NewEncoder(w).Encode(map[string]any{
            "status": "ok",
            "uptime": cfg.Uptime().String(),
            "time":   time.Now().UTC().Format(time.RFC3339),
        })
    })

    srv := &http.Server{
        Addr:              ":8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       120 * time.Second,
    }

    fmt.Println("config server listening on :8080")
    if err := srv.ListenAndServe(); err != nil {
        fmt.Printf("server error: %v\n", err)
    }
}

Test It

# Start the server
go run ./...

# Get all config
curl http://localhost:8080/config

# Update a value
curl -X PUT http://localhost:8080/config/waf_mode \
  -H "Content-Type: application/json" \
  -d '{"value":"blocking"}'

# Health check
curl http://localhost:8080/health

Engineer Takeaway

You can now make the everyday Go choices deliberately: var vs :=, pointer vs value receivers, and explicit error returns instead of exceptions. This Config Server is also the admin interface you'll add to your WAF in Week 10 — hot-reload rules without restarting the server.

Generated from week1.md · 8 Oct 2026 16:36