Introduction
Go (Golang) is a modern compiled language from Google that is ideal for creating high-load network services. The built-in net/http package provides a powerful and flexible HTTP client that allows you to easily interact with REST APIs, web servers, and other HTTP resources. However, despite its apparent simplicity, beginners often make typical mistakes: they forget to close the response body, do not handle timeouts, or ignore errors.
In this article, we will break down how to properly use the HTTP client in Go. You will learn how to make GET and POST requests, manage headers, configure timeouts, and handle errors. All code examples are ready to use in your projects.
1. Basics: GET request and reading the response
The simplest way to perform a GET request is to use the http.Get() function. However, for production code, it is recommended to create your own http.Client instance — this gives you more control over the client's behavior.
Simple GET request
package main
import ( "fmt" "io" "net/http" "os")
func main() { // Create a client (with default settings for now) client := &http.Client{} // Perform a GET request resp, err := client.Get("https://api.example.com/data") if err != nil { fmt.Fprintf(os.Stderr, "Request error: %v", err) os.Exit(1) } // IMPORTANT: close the response body defer resp.Body.Close()
// Read the response body body, err := io.ReadAll(resp.Body) if err != nil { fmt.Fprintf(os.Stderr, "Error reading body: %v", err) os.Exit(1) }
fmt.Printf("Status: %s", resp.Status) fmt.Printf("Body: %s", body)}Key points:
defer resp.Body.Close()— always close the response body, otherwise a resource leak will occur (the connection will not return to the pool).- Always check
err— the network is unstable, and the request may fail. - Check the HTTP response status (
resp.StatusCode) to distinguish success (2xx) from server errors (4xx, 5xx).
2. POST request with body and headers
To send data to the server (e.g., JSON), use http.Post() or the more flexible method http.NewRequest() followed by a call to client.Do(). The second method is preferable as it allows you to set headers and other parameters.
POST with JSON data
package main
import ( "bytes" "encoding/json" "fmt" "net/http" "os")
type User struct { Name string `json:"name"` Email string `json:"email"`}
func main() { client := &http.Client{}
// Prepare the data user := User{Name: "Ivan Petrov", Email: "ivan@example.com"} jsonData, err := json.Marshal(user) if err != nil { fmt.Fprintf(os.Stderr, "JSON serialization error: %v", err) os.Exit(1) }
// Create the request req, err := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(jsonData)) if err != nil { fmt.Fprintf(os.Stderr, "Error creating request: %v", err) os.Exit(1) }
// Set headers req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer YOUR_TOKEN_HERE")
// Send the request resp, err := client.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "Request error: %v", err) os.Exit(1) } defer resp.Body.Close()
// Check the status if resp.StatusCode != http.StatusCreated { fmt.Fprintf(os.Stderr, "Server returned an error: %s", resp.Status) os.Exit(1) }
fmt.Println("User successfully created!")}Important