Skip to content

Go HTTP Client — Making HTTP Requests with net/http Client and Transport

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Go HTTP Client. We cover key concepts, practical examples, and best practices to help you master this topic.

Go http.Client makes HTTP requests with configurable Transport, timeouts, and connection pooling for efficient API communication.

What You'll Learn

  • Making GET and POST requests
  • Configuring http.Client
  • JSON request/response handling
  • Connection pooling and reuse

Why It Matters

HTTP clients are essential for API integration. Docker uses HTTP clients for registry communication. Kubernetes uses clients for API server interaction. DodaZIP uses HTTP clients for external API calls.

Real-World Use

API client libraries, microservice communication, web scraping, cloud provider SDKs.

flowchart LR
    A["HTTP Client"] --> B["http.Get"]
    A --> C["http.Post"]
    A --> D["Custom Client"]
    A --> E["Transport"]
    A:::current --> B
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b

Basic GET Request

func main() {
    resp, err := http.Get("https://api.github.com")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

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

POST with JSON

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

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

    resp, err := http.Post(
        "https://api.example.com/users",
        "application/json",
        bytes.NewBuffer(jsonData),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    var created User
    json.NewDecoder(resp.Body).Decode(&created)
    fmt.Printf("Created: %+v\n", created)
}

Custom HTTP Client

func main() {
    client := &http.Client{
        Timeout: 10 * time.Second,
        Transport: &http.Transport{
            MaxIdleConns:        100,
            MaxIdleConnsPerHost: 10,
            IdleConnTimeout:     90 * time.Second,
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: false,
            },
        },
    }

    req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
    req.Header.Set("Authorization", "Bearer token123")
    req.Header.Set("Accept", "application/json")

    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
}

JSON Response Parsing

type GitHubUser struct {
    Login     string `json:"login"`
    Name      string `json:"name"`
    Followers int    `json:"followers"`
}

func fetchUser(username string) (*GitHubUser, error) {
    url := fmt.Sprintf("https://api.github.com/users/%s", username)
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("API error: %s", resp.Status)
    }

    var user GitHubUser
    if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
        return nil, err
    }
    return &user, nil
}

func main() {
    user, _ := fetchUser("golang")
    fmt.Printf("%s has %d followers\n", user.Name, user.Followers)
}

Multipart Form Upload

func main() {
    var buf bytes.Buffer
    writer := multipart.NewWriter(&buf)

    file, _ := os.Open("photo.jpg")
    defer file.Close()

    part, _ := writer.CreateFormFile("photo", "photo.jpg")
    io.Copy(part, file)
    writer.Close()

    resp, _ := http.Post("https://api.example.com/upload",
        writer.FormDataContentType(), &buf)
    defer resp.Body.Close()
    fmt.Println("Upload status:", resp.Status)
}

Common Mistakes

1. Not Closing Response Body

resp, _ := http.Get(url)
// defer resp.Body.Close()  // Missing! Connection leak
// Body must be closed even if not read

2. Ignoring Context

// Bad: No timeout, can block forever
http.Get(url)

// Good: With context timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
client.Do(req)

3. Not Checking Status Code

resp, _ := http.Get(url)
// Always check resp.StatusCode before processing body
if resp.StatusCode != http.StatusOK { /* handle error */ }

4. Reusing Body After Close

Read body once. If you need to read again, copy to buffer first.

5. Not Using Default Client for Production

Default client has no timeout. Always create a custom client with appropriate timeouts for production.

Practice Questions

1. What is http.DefaultClient? A package-level client with default settings. Has no timeout — not recommended for production.

2. How do you set request headers? Create a request with http.NewRequest, then set headers on req.Header before calling client.Do.

3. What does http.Transport manage? Connection pooling, TLS config, proxy settings, and keep-alive behavior.

4. How do you handle redirects? Use client.CheckRedirect to control redirect behavior. Return an error to prevent redirect.

Challenge: Write a function that retries failed HTTP requests with exponential backoff.

Solution
func fetchWithRetry(url string, maxRetries int) (*http.Response, error) {
    client := &http.Client{Timeout: 5 * time.Second}
    var resp *http.Response

    for i := 0; i < maxRetries; i++ {
        var err error
        resp, err = client.Get(url)
        if err == nil && resp.StatusCode < 500 {
            return resp, nil
        }
        if resp != nil { resp.Body.Close() }
        time.Sleep(time.Duration(math.Pow(2, float64(i))) * 100 * time.Millisecond)
    }
    return nil, fmt.Errorf("max retries exceeded")
}

FAQ

{{< faq question="How do I make a POST with form data?" >}} Use http.PostForm(url, url.Values{"key": {"value"}}) or manually set Content-Type to application/x-www-form-urlencoded. {{< /faq >}}

{{< faq question="What is http.Client round trip?" >}} The Transport.RoundTrip method executes a single HTTP Transaction. Client.Do calls RoundTrip internally. {{< /faq >}}

{{< faq question="How do I set a cookie?" >}} Create a cookiejar. Use client.Jar to store and send cookies automatically. Or set Cookie header manually. {{< /faq >}}

{{< faq question="How do I handle HTTPS with custom certificates?" >}} Set Transport.TLSClientConfig.RootCAs with custom CA cert. Use InsecureSkipVerify: true only for testing. {{< /faq >}}

{{< faq question="Can I reuse the same client across goroutines?" >}} Yes. http.Client is safe for concurrent use. Transport manages connection pooling internally. {{< /faq >}}

Try It Yourself

package main

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

func main() {
    resp, err := http.Get("https://httpbin.org/get")
    if err != nil { fmt.Println(err); return }
    defer resp.Body.Close()

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

Expected output — HTTP 200 response with JSON body from httpbin.org.

What's Next

Now that you understand HTTP clients, explore database operations in Go.

Topic Description Link
Go Database SQL SQL database operations {{< ref "29-database-sql" >}}
Go HTTP Servers Building web servers {{< ref "27-http-server" >}}
Go JSON JSON encoding/decoding {{< ref "26-json" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse Go