The Other Side of the API
Go programs are not only servers — they call APIs too: fetching data, talking to services, integrating third parties. The net/http client side is three habits:
1. make the request (http.Get, http.Post, or http.NewRequest for full control),
2. always defer resp.Body.Close(),
3. check the status code before trusting the body.
The demos below call a local test server, so the outputs are stable — the client code is exactly what you would point at a real URL.
GET and Decode
The everyday task: GET a URL, check the status, decode the JSON straight from the body into structs with json.NewDecoder(resp.Body).Decode(&value) — no intermediate string needed.
Fetch a list into structs
The response body decodes directly into []User, and from there it is ordinary Go data — looped and printed field by field.
Output: Go program and captured output
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `[{"id": 1, "name": "Ana"}, {"id": 2, "name": "Ben"}]`)
}))
defer server.Close()
resp, err := http.Get(server.URL + "/users")
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Println("unexpected status:", resp.Status)
return
}
var users []User
if err := json.NewDecoder(resp.Body).Decode(&users); err != nil {
fmt.Println("bad JSON:", err)
return
}
for _, u := range users {
fmt.Printf("user %d: %s\n", u.ID, u.Name)
}
}
POST with a JSON Body
Sending data is the mirror image: marshal the struct, wrap the bytes in a reader, POST with the right content type — then decode the server's answer, which here carries the id the server assigned.
Send a struct, read the answer
The struct goes out as JSON and the created record comes back with its new id — the complete write-path of an API client in one small program.
Output: Go program and captured output
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var u User
json.NewDecoder(r.Body).Decode(&u)
u.ID = 7 // the server assigns the id
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(u)
}))
defer server.Close()
payload, _ := json.Marshal(User{Name: "Cleo"})
resp, err := http.Post(server.URL+"/users", "application/json",
bytes.NewReader(payload))
if err != nil {
fmt.Println("request failed:", err)
return
}
defer resp.Body.Close()
var created User
json.NewDecoder(resp.Body).Decode(&created)
fmt.Println("status: ", resp.Status)
fmt.Printf("created: %+v\n", created)
}