HTTP client in Go: making requests correctly from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why it's important to know how to work with HTTP in Go

\n

Go (Golang) is a language designed for network programming. Its standard library net/http provides powerful and flexible tools for creating both servers and clients. However, many developers, especially those coming from scripting languages, make common mistakes: they forget to close the response body, don't set timeouts, or handle errors incorrectly.

\n

In this article, we will cover creating an HTTP client in Go "from A to Z". You will learn how to properly send GET and POST requests, manage headers, configure timeouts, and handle errors. All code examples are ready to use.

\n\n

1. Basics: creating a client and the first GET request

\n

In Go, the HTTP client is the http.Client struct. You can use the global client http.DefaultClient or create your own with custom settings. Let's start with a simple GET request.

\n
package main\n\nimport (\n    "fmt"\n    "io"\n    "net/http"\n    "os"\n)\n\nfunc main() {\n    // Use the built-in client\n    resp, err := http.Get("https://api.example.com/data")\n    if err != nil {\n        fmt.Printf("Request error: %v\\n", err)\n        os.Exit(1)\n    }\n    defer resp.Body.Close() // IMPORTANT: always close the response body!\n\n    // Read the response body\n    body, err := io.ReadAll(resp.Body)\n    if err != nil {\n        fmt.Printf("Error reading response: %v\\n", err)\n        os.Exit(1)\n    }\n\n    fmt.Printf("Status: %s\\n", resp.Status)\n    fmt.Printf("Body: %s\\n", string(body))\n}
\n

Key points:

\n
    \n
  • Always close resp.Body using defer. Otherwise, resource leaks will occur (connections won't return to the pool).
  • \n
  • Check the response status (resp.StatusCode). Code 200 is not the only successful one (there are 201, 204, etc.).
  • \n
  • io.ReadAll reads the entire body into memory. For large responses, use io.Copy or streaming processing.
  • \n
\n\n

2. Advanced client: timeouts, headers, and custom settings

\n

The global client has no timeouts by default — this can cause the program to hang. Always create your own client with settings.

\n

2.1. Creating a client with a timeout

\n
client := &http.Client{\n    Timeout: 10 * time.Second, // Total timeout for the entire request\n}\n\nresp, err := client.Get("https://api.example.com/data")
\n

You can also configure timeouts at different levels via Transport:

\n
transport := &http.Transport{\n    DialContext: (&net.Dialer{\n        Timeout:   5 * time.Second, // Connection timeout\n        KeepAlive: 30 * time.Second,\n    }).DialContext,\n    TLSHandshakeTimeout:   5 * time.Second,\n    ResponseHeaderTimeout: 5 * time.Second,\n}\n\nclient := &http.Client{\n    Transport: transport,\n    Timeout:   15 * time.Second,\n}
\n\n

2.2. Adding headers and parameters

\n

For complex requests, use http.NewRequest:

\n
req, err := http.NewRequest("GET", "https://api.example.com/search", nil)\nif err != nil {\n    log.Fatal(err)\n}\n\n// Add headers\nreq.Header.Set("Authorization", "Bearer your-token")\nreq.Header.Set("Accept", "application/json")\n\n// Add query parameters\nq := req.URL.Query()\nq.Add("query", "golang")\nq.Add("page", "1")\nreq.URL.RawQuery = q.Encode()\n\nresp, err := client.Do(req)
\n\n

3. Working with POST and other methods (JSON, forms)

\n

POST requests require sending a body. Let's look at two popular formats: JSON and URL-encoded form.

\n

3.1. POST with JSON

\n
type RequestData struct {\n    Name  string `json:"name"`\n    Email string `json:"email"`\n}\n\ndata := RequestData{\n    Name:  "Ivan",\n    Email: "ivan@example.com",\n}\n\njsonData, err := json.Marshal(da                

Blogs

Book Recommendations