020 Project 20: KV HTTP Store
020 Build a Key-Value HTTP Store
A production-style mini service with CRUD endpoints and periodic snapshot persistence.
HTTP API <-> in-memory map + mutex <-> snapshot.json
Full main.go
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"sync"
"time"
)
type store struct {
mu sync.RWMutex
m map[string]string
}
func (s *store) load(path string) {
b, err := os.ReadFile(path)
if err != nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
_ = json.Unmarshal(b, &s.m)
}
func (s *store) save(path string) error {
s.mu.RLock()
defer s.mu.RUnlock()
b, err := json.MarshalIndent(s.m, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0o644)
}
func main() {
s := &store{m: map[string]string{}}
snap := "snapshot.json"
s.load(snap)
go func() {
t := time.NewTicker(10 * time.Second)
defer t.Stop()
for range t.C {
_ = s.save(snap)
}
}()
mux := http.NewServeMux()
mux.HandleFunc("PUT /kv/{key}", func(w http.ResponseWriter, r *http.Request) {
key := r.PathValue("key")
var body struct{ Value string `json:"value"` }
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad json", http.StatusBadRequest)
return
}
s.mu.Lock()
s.m[key] = body.Value
s.mu.Unlock()
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /kv/{key}", func(w http.ResponseWriter, r *http.Request) {
key := r.PathValue("key")
s.mu.RLock()
v, ok := s.m[key]
s.mu.RUnlock()
if !ok {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode(map[string]string{"key": key, "value": v})
})
mux.HandleFunc("DELETE /kv/{key}", func(w http.ResponseWriter, r *http.Request) {
key := r.PathValue("key")
s.mu.Lock()
delete(s.m, key)
s.mu.Unlock()
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("GET /kv", func(w http.ResponseWriter, r *http.Request) {
s.mu.RLock()
defer s.mu.RUnlock()
_ = json.NewEncoder(w).Encode(s.m)
})
log.Println("listening :8081")
log.Fatal(http.ListenAndServe(":8081", mux))
}Run
go run .
curl -X PUT localhost:8081/kv/name -d '{"value":"king"}' -H 'content-type: application/json'
curl localhost:8081/kv/nameStep-by-Step Explanation
- Define request and response contracts first.
- Validate inbound input before doing any state changes.
- Keep handler logic short and move reusable logic into helper functions.
- Add timeouts and clear error paths.
- Return consistent responses and status codes.
Code Anatomy
- Handlers parse input, call domain logic, write response.
- Shared state uses synchronization where needed.
- Transport concerns stay separate from business rules.
Learning Goals
- Build reliable service endpoints in Go.
- Understand API ergonomics and operational safety.
- Prepare code structure for tests and persistence later.