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.