Go HTTP Client

Go's net/http package includes a built-in HTTP client for making requests to external APIs and web services. The client handles GET, POST, PUT, DELETE, and other HTTP methods. It manages connections, redirects, and timeouts automatically.

Making a GET Request

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    resp, err := http.Get("https://httpbin.org/get")
    if err != nil {
        fmt.Println("Request failed:", err)
        return
    }
    defer resp.Body.Close() // always close the response body

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading body:", err)
        return
    }

    fmt.Println("Status:", resp.Status)
    fmt.Println("Body:", string(body))
}

HTTP Client Flow

Go Program
    │
    │  http.Get(url)
    ▼
HTTP Request ──────────────────► External Server
    │                                   │
    │                                   │  processes request
    │  response (status + body)         │
    ◄───────────────────────────────────┘
    │
    ▼
resp.Body  → io.ReadAll() → []byte → string

Reading the Response

Field / MethodDescription
resp.StatusStatus text, e.g. "200 OK"
resp.StatusCodeStatus number, e.g. 200
resp.HeaderMap of response headers
resp.BodyResponse body — must be closed after reading

Making a POST Request with JSON

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    user := User{Name: "Alice", Email: "alice@example.com"}

    jsonData, err := json.Marshal(user)
    if err != nil {
        fmt.Println("JSON error:", err)
        return
    }

    resp, err := http.Post(
        "https://httpbin.org/post",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        fmt.Println("Request failed:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println("Status:", resp.StatusCode)
    fmt.Println("Response:", string(body))
}

Using a Custom HTTP Client with Timeout

The default http.Get has no timeout — it can wait forever. Always use a custom client with a timeout for production code.

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    client := &http.Client{
        Timeout: 5 * time.Second, // cancel request after 5 seconds
    }

    resp, err := client.Get("https://httpbin.org/delay/2")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Setting Custom Headers

package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    req, err := http.NewRequest("GET", "https://httpbin.org/headers", nil)
    if err != nil {
        fmt.Println("Request error:", err)
        return
    }

    req.Header.Set("Authorization", "Bearer mytoken123")
    req.Header.Set("Accept", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Println(string(body))
}

Parsing JSON from a Response

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type Post struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
    Body  string `json:"body"`
}

func main() {
    resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer resp.Body.Close()

    var post Post
    json.NewDecoder(resp.Body).Decode(&post)

    fmt.Println("ID:", post.ID)
    fmt.Println("Title:", post.Title)
}

HTTP Methods Summary

MethodGo FunctionPurpose
GEThttp.Get(url)Retrieve data
POSThttp.Post(url, contentType, body)Send data to create a resource
PUT / PATCH / DELETEhttp.NewRequest(method, url, body)Custom method with full control

Key Points

  • Always call defer resp.Body.Close() after receiving a response to avoid resource leaks
  • Use a custom http.Client with a Timeout to prevent requests from hanging forever
  • Use http.NewRequest when custom headers or non-standard methods are needed
  • Use json.NewDecoder(resp.Body).Decode(&target) to parse JSON responses directly
  • Always check resp.StatusCode to verify the server processed the request successfully

Leave a Comment