Crash Courses · PhD
Full REST API
In-memory CRUD API with routing, middleware, and error handling
60 min read+200 XP on completionCert: Go Crash Course
Tap any word in the text below to start reading from there.
Full REST API
Project Layout
api/
main.goComplete In-Memory REST API
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
// ── Model ──────────────────────────────────────────────────────────────────
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
// ── Store ──────────────────────────────────────────────────────────────────
type Store struct {
mu sync.RWMutex
products map[int]Product
nextID int
}
func NewStore() *Store {
s := &Store{products: make(map[int]Product), nextID: 1}
s.products[1] = Product{ID: 1, Name: "Go T-Shirt", Price: 25.00}
s.nextID = 2
return s
}
func (s *Store) List() []Product {
s.mu.RLock()
defer s.mu.RUnlock()
list := make([]Product, 0, len(s.products))
for _, p := range s.products {
list = append(list, p)
}
return list
}
func (s *Store) Get(id int) (Product, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
p, ok := s.products[id]
return p, ok
}
func (s *Store) Create(p Product) Product {
s.mu.Lock()
defer s.mu.Unlock()
p.ID = s.nextID
s.nextID++
s.products[p.ID] = p
return p
}
func (s *Store) Update(id int, p Product) (Product, bool) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.products[id]; !ok {
return Product{}, false
}
p.ID = id
s.products[id] = p
return p, true
}
func (s *Store) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.products[id]; !ok {
return false
}
delete(s.products, id)
return true
}
// ── Helpers ────────────────────────────────────────────────────────────────
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
func idFromPath(path string) (int, bool) {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) < 2 {
return 0, false
}
id, err := strconv.Atoi(parts[len(parts)-1])
return id, err == nil
}
// ── Handlers ───────────────────────────────────────────────────────────────
func productsHandler(store *Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
writeJSON(w, http.StatusOK, store.List())
case http.MethodPost:
var p Product
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
created := store.Create(p)
writeJSON(w, http.StatusCreated, created)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
}
func productHandler(store *Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, ok := idFromPath(r.URL.Path)
if !ok {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
p, found := store.Get(id)
if !found {
http.Error(w, "not found", http.StatusNotFound)
return
}
writeJSON(w, http.StatusOK, p)
case http.MethodPut:
var p Product
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
updated, found := store.Update(id, p)
if !found {
http.Error(w, "not found", http.StatusNotFound)
return
}
writeJSON(w, http.StatusOK, updated)
case http.MethodDelete:
if !store.Delete(id) {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusNoContent)
default:
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
}
}
}
// ── Middleware ─────────────────────────────────────────────────────────────
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}
// ── Main ───────────────────────────────────────────────────────────────────
func main() {
store := NewStore()
mux := http.NewServeMux()
mux.HandleFunc("/products", productsHandler(store))
mux.HandleFunc("/products/", productHandler(store))
server := &http.Server{
Addr: ":8080",
Handler: logging(mux),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
fmt.Println("API running on http://localhost:8080")
log.Fatal(server.ListenAndServe())
}Test with curl
# List products
curl http://localhost:8080/products
# Create
curl -X POST -H "Content-Type: application/json" \
-d '{"name":"Gopher Mug","price":12.99}' \
http://localhost:8080/products
# Get one
curl http://localhost:8080/products/1
# Update
curl -X PUT -H "Content-Type: application/json" \
-d '{"name":"Go Hoodie","price":45.00}' \
http://localhost:8080/products/1
# Delete
curl -X DELETE http://localhost:8080/products/1Reading progress
0% read
In this module
CRUD
REST
middleware
sync.RWMutex
0%