A golang post request with a JSON body is the usual way to call REST APIs from Go: you create a POST, attach UTF-8 JSON bytes, set Content-Type to application/json, then read the response with io.ReadAll. This page is a compact golang http post example using only net/http and encoding/json—no third-party HTTP client. For wider client and server patterns, see HTTP in Go; for decoding arbitrary JSON into structs or map[string]any, see parse JSON in Go.
Checked with Go 1.24 on 64-bit Linux (Fedora and Ubuntu family).
golang http post json with net/http
The flow for send post request golang code matches most languages: marshal a value to JSON, open an http.Request with a body io.Reader (often bytes.NewReader), set headers, then Client.Do. On the server, json.NewDecoder(r.Body).Decode is the common way to read the golang post json payload without buffering the whole body first.
Self-contained golang http post example (httptest)
The snippet below starts an in-memory server with httptest.NewServer, sends one golang http post with JSON, and prints the status line plus body. Save it as main.go, run go mod init example once in that folder, then go run .—no separate ListenAndServe on a real port.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
)
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}
type loginResponse struct {
Message string `json:"message"`
}
func handler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
defer r.Body.Close()
var req loginRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
if req.Username == "user1" && req.Password == "pw1" {
_ = json.NewEncoder(w).Encode(loginResponse{Message: "ok"})
return
}
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(loginResponse{Message: "bad credentials"})
}
func main() {
srv := httptest.NewServer(http.HandlerFunc(handler))
defer srv.Close()
payload, err := json.Marshal(loginRequest{Username: "user1", Password: "pw1"})
if err != nil {
log.Fatal(err)
}
req, err := http.NewRequest(http.MethodPost, srv.URL, bytes.NewReader(payload))
if err != nil {
log.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(res.StatusCode, string(body))
}You should see a 200 line and JSON like {"message":"ok"}.
Request body: raw []byte vs json.Marshal
Either shape is valid for golang post json:
- Fixed literals: build
[]bytefrom a Go raw string literal that contains your JSON so you avoid doubling backslashes on quotes. - Dynamic data: build a struct or map and
json.Marshal(v)so field names and escaping stay correct.
Always pass the body reader to http.NewRequest before you send; if json.Marshal fails, do not call the API with a partial buffer.
Client details people miss
Check the error from http.NewRequest before calling Header.Set—if construction failed, req may be nil. Prefer http.MethodPost over the string "POST" for clarity. For production go post request code, attach a deadline with context.WithTimeout and http.NewRequestWithContext so slow APIs cannot hang your worker forever.
Summary
This golang http post example shows how to perform a golang post request with json body: marshal with encoding/json, post with http.NewRequest and bytes.NewReader, set Content-Type to application/json, and read the reply with io.ReadAll. The server side decodes JSON with json.NewDecoder on r.Body and writes JSON back with json.NewEncoder. Together that covers the intent behind searches like golang post request, golang http post json, send post request golang, and post request golang—without a separate terminal server or deprecated ioutil helpers.

