A Small, Complete API
Time to assemble the pieces: a users API with the five classic endpoints —
POST /users create · GET /users list · GET /users/{id} read one · PUT /users/{id} replace · DELETE /users/{id} remove.
The store is an in-memory map, so the example stays self-contained. (A real server handles requests concurrently, so a production version would guard the map with a lock and probably swap it for a database — the handlers would not change shape.)
Create and List
POST decodes the incoming JSON, gives the user an id, stores it, and answers 201 with the created record — id included, so the client learns it. GET returns everyone as a JSON array.
POST /users and GET /users
Two creations, then the list: each POST answers 201 with the stored user, and the final GET shows both — the API's state grew with each request.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
users := map[int]User{}
nextID := 1
mux := http.NewServeMux()
mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
u.ID = nextID
nextID++
users[u.ID] = u
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(u)
})
mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
list := []User{}
for id := 1; id < nextID; id++ {
if u, ok := users[id]; ok {
list = append(list, u)
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(list)
})
server := httptest.NewServer(mux)
defer server.Close()
for _, name := range []string{"Ana", "Ben"} {
resp, _ := http.Post(server.URL+"/users", "application/json",
strings.NewReader(`{"name": "`+name+`"}`))
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println("POST /users ->", resp.StatusCode, strings.TrimSpace(string(body)))
}
resp, _ := http.Get(server.URL + "/users")
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Println("GET /users ->", resp.StatusCode, strings.TrimSpace(string(body)))
}
Read One, Replace, Delete
The {id} routes complete the set: read answers 200 or 404, replace decodes the new data into the existing id, delete answers 204. After the delete, the same GET that worked before honestly returns 404 — the resource is gone.
GET, PUT and DELETE on /users/{id}
The full life of one resource: read it, replace it (the response carries the new name), delete it, and fail to read it — every step with the correct status code.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
users := map[int]User{1: {ID: 1, Name: "Ana"}}
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
w.Header().Set("Content-Type", "application/json")
u, ok := users[id]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintln(w, `{"error": "user not found"}`)
return
}
json.NewEncoder(w).Encode(u)
})
mux.HandleFunc("PUT /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, "invalid JSON", http.StatusBadRequest)
return
}
u.ID = id
users[id] = u
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(u)
})
mux.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(r.PathValue("id"))
delete(users, id)
w.WriteHeader(http.StatusNoContent)
})
server := httptest.NewServer(mux)
defer server.Close()
client := &http.Client{}
do := func(method, path, payload string) {
var reqBody io.Reader
if payload != "" {
reqBody = strings.NewReader(payload)
}
req, _ := http.NewRequest(method, server.URL+path, reqBody)
resp, _ := client.Do(req)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Printf("%-6s %s -> %d %s\n", method, path, resp.StatusCode,
strings.TrimSpace(string(body)))
}
do("GET", "/users/1", "")
do("PUT", "/users/1", `{"name": "Ana Maria"}`)
do("DELETE", "/users/1", "")
do("GET", "/users/1", "")
}