Question

How to Set Headers in an HTTP GET Request in Go

gohttp

Question

I am making a simple HTTP GET request in Go:

client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
res, _ := client.Do(req)

How can I customize the request headers? I could not find a clear way to do that in the documentation.

Short Answer

By the end of this page, you will understand how to add custom headers to an HTTP GET request in Go using the net/http package. You will also learn the difference between creating a request and sending it, how headers are stored, common mistakes to avoid, and how this pattern is used in real Go codebases.

Concept

In Go, HTTP headers are part of the http.Request object. That means you do not pass headers directly into client.Do(). Instead, you:

  1. Create a request with http.NewRequest()
  2. Add or modify headers on the request
  3. Send the request with client.Do()

A header is just metadata sent with the request. Common examples include:

  • Authorization for tokens or API keys
  • Accept to say what response format you want
  • User-Agent to identify the client
  • Content-Type for request bodies

In Go, headers are stored in req.Header, which is a map-like structure of type http.Header.

Example:

req.Header.Set("Authorization", "Bearer my-token")
req.Header.Set("Accept", "application/json")

This matters because many real APIs require headers for:

  • authentication
  • versioning
  • content negotiation
  • tracing and debugging
  • rate limiting or client identification

If you do not set the required headers, the server may reject your request or return unexpected results.

Mental Model

Think of an HTTP request like mailing a package.

  • The URL is the destination address.
  • The method (GET, POST) is the type of delivery.
  • The body is the package contents.
  • The headers are the labels on the box.

For example:

  • Authorization is like a security badge.
  • Accept: application/json is like saying, "Please reply in JSON format."
  • User-Agent is like writing who sent the package.

In Go, you first prepare the box (http.NewRequest), then attach labels (req.Header.Set(...)), and only then send it (client.Do(req)).

Take Quiz

Syntax and Examples

The basic syntax is:

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    // handle error
}

req.Header.Set("Header-Name", "value")

res, err := client.Do(req)
if err != nil {
    // handle error
}
defer res.Body.Close()

Example: Add custom headers to a GET request

package main

import (
    "fmt"
    "net/http"
)

func main() {
    client := &http.Client{}

    req, err := http.NewRequest("GET", "https://api.example.com/users", nil)
    if err != nil {
        fmt.Println("request creation error:", err)
        return
    }

    req.Header.Set("Authorization", "Bearer my-token")
    req.Header.Set("Accept", "application/json")
    req.Header.Set("User-Agent", "my-go-app/1.0")

    res, err := client.Do(req)
    if err != nil {
        fmt.Println("request error:", err)
        return
    }
    defer res.Body.Close()

    fmt.Println("Status:", res.Status)
}

Set vs Add

Go provides two common ways to insert headers:

req.Header.Set("Accept", "application/json")
req.Header.Add("Accept", "text/plain")
  • Set replaces any existing value for that header.
  • Add appends another value for the same header.

Use Set most of the time unless you intentionally want multiple values.

Step by Step Execution

Consider this example:

client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
if err != nil {
    return
}

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

res, err := client.Do(req)
if err != nil {
    return
}
defer res.Body.Close()

Here is what happens step by step:

  1. client := &http.Client{}

    • Creates an HTTP client that can send requests.
  2. http.NewRequest("GET", "https://api.example.com/data", nil)

    • Creates a new request object.
    • The method is GET.
    • The URL is https://api.example.com/data.
    • The body is nil because a simple GET request usually has no body.
  3. req.Header.Set("Accept", "application/json")

    • Adds an Accept header.
    • This tells the server that JSON is preferred.
  4. req.Header.Set("Authorization", "Bearer abc123")

    • Adds an authorization token.
    • Many APIs require this header to identify the caller.
  5. client.Do(req)

    • Sends the request, including the headers.
    • The server receives the method, URL, and header values.
  6. defer res.Body.Close()

    • Ensures the response body is closed after use.
    • This is important to avoid resource leaks.

The key idea is that the headers must be added before client.Do(req) is called.

Real World Use Cases

Custom request headers are used in many practical situations:

Calling authenticated APIs

req.Header.Set("Authorization", "Bearer "+token)

Used when talking to services like GitHub, Stripe, or internal company APIs.

Asking for JSON responses

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

Useful when an API can return multiple formats.

Sending API keys

req.Header.Set("X-API-Key", apiKey)

Some services use custom headers instead of bearer tokens.

Identifying your client

req.Header.Set("User-Agent", "inventory-sync/2.1")

Helpful for logs, monitoring, or service rules.

Tracing requests across services

req.Header.Set("X-Request-ID", requestID)

Common in distributed systems and microservices.

Take Quiz

Real Codebase Usage

In real Go projects, developers usually do more than just set one header manually.

Validation before sending requests

A common pattern is to validate required input first:

if token == "" {
    return fmt.Errorf("missing token")
}

Then build the request and set headers.

Helper functions

Teams often wrap request creation in reusable functions:

func newAPIRequest(method, url, token string) (*http.Request, error) {
    req, err := http.NewRequest(method, url, nil)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+token)
    req.Header.Set("Accept", "application/json")
    return req, nil
}

This keeps header logic consistent.

Guard clauses and early returns

Instead of deeply nested code, Go codebases often use early error checks:

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    return err
}

res, err := client.Do(req)
if err != nil {
    return err
}

Shared clients

A single http.Client is often reused instead of creating a new one for every request.

var client = &http.Client{}

This is more common in production code.

Configuration-driven headers

Some headers come from config or environment variables:

req.Header.Set("X-API-Key", os.Getenv("API_KEY"))

This keeps secrets out of source code.

Common Mistakes

Here are common beginner mistakes when setting headers in Go.

1. Forgetting to set headers on the request

Broken code:

client := &http.Client{}
res, err := client.Do(req)
req.Header.Set("Authorization", "Bearer token")

Problem:

  • The request was already sent.
  • Setting headers afterward does nothing for that request.

Fix:

req.Header.Set("Authorization", "Bearer token")
res, err := client.Do(req)

2. Ignoring errors from http.NewRequest

Broken code:

req, _ := http.NewRequest("GET", url, nil)

Problem:

  • If request creation fails, you may get unexpected behavior later.

Fix:

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    return err
}

3. Forgetting to close the response body

Broken code:

res, err := client.Do(req)
if err != nil {
    return err
}

Problem:

  • The response body stays open.
  • This can leak resources.

Fix:

res, err := client.Do(req)
if err != nil {
    return err
}
defer res.Body.Close()

4. Using Add when Set is intended

Broken code:

req.Header.Add("Authorization", "Bearer token1")
req.Header.Add("Authorization", "Bearer token2")

Problem:

  • You now have multiple authorization values.
  • That is usually invalid.

Fix:

req.Header.Set("Authorization", "Bearer token")

5. Setting Content-Type on a GET request without a body

This is not always wrong, but it often has no purpose. Content-Type describes the request body format. For a plain GET with no body, Accept is often the more relevant header.

6. Misspelling header names or values

Example:

req.Header.Set("Authorisation", "Bearer token")

Many APIs expect exact standard names like Authorization. Use the documented header name from the API.

Comparisons

Here is how related HTTP request concepts compare in Go:

ConceptPurposeExampleCommon Use
req.Header.Set()Set or replace a header valuereq.Header.Set("Accept", "application/json")Most common choice
req.Header.Add()Add another value for the same headerreq.Header.Add("Cache-Control", "no-cache")Multiple values when needed
http.NewRequest()Create a request objecthttp.NewRequest("GET", url, nil)First step before sending
client.Do(req)Send the prepared requestclient.Do(req)Executes the request
http.Get()Quick GET helperhttp.Get(url)Simple requests without customization

http.Get() vs http.NewRequest()

OptionBest forLimitation
http.Get(url)Very simple GET requestsHarder to customize headers before sending
http.NewRequest() + client.Do()Flexible and production-style requestsSlightly more code

If you need custom headers, http.NewRequest() is usually the right choice.

Take Quiz

Cheat Sheet

req, err := http.NewRequest("GET", url, nil)
if err != nil {
    return err
}

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

res, err := client.Do(req)
if err != nil {
    return err
}
defer res.Body.Close()

Quick rules

  • Use req.Header.Set(key, value) to set a header.
  • Set headers before calling client.Do(req).
  • Use Set to replace a value.
  • Use Add to append another value.
  • Always check errors from http.NewRequest and client.Do.
  • Always close res.Body.
  • Use Accept for expected response format.
  • Use Content-Type mainly when sending a request body.

Common headers

  • Authorization
  • Accept
  • User-Agent
  • X-API-Key
  • X-Request-ID

Best practice

Prefer http.NewRequest() over http.Get() when you need custom headers or more control.

FAQ

How do I add headers to a GET request in Go?

Create the request with http.NewRequest(), then use req.Header.Set("Name", "Value") before sending it with client.Do(req).

Can I set headers with http.Get() in Go?

Not directly in a clean way. If you need custom headers, create a request manually with http.NewRequest().

What is the difference between Set and Add for headers in Go?

Set replaces any existing value. Add appends another value under the same header name.

Do I need Content-Type for a GET request?

Usually no, because GET requests often have no body. Accept is often more useful for GET requests.

Why is my Authorization header not being sent?

A common reason is setting the header after calling client.Do(req). Headers must be added before sending the request.

Are header names case-sensitive in Go?

Go handles headers in a canonical form, and HTTP header names are generally treated case-insensitively. Still, use standard names like Authorization and Accept for clarity.

Should I reuse http.Client in Go?

Yes, in most real applications you should reuse clients instead of creating a new one for every request.

How do I send multiple headers in Go?

Call req.Header.Set() multiple times, once for each header you want to include.

Take Quiz

Mini Project

Description

Build a small Go program that fetches user data from an API endpoint using a custom Authorization header and an Accept header. This demonstrates the full request flow: creating a request, setting headers, sending it, and handling the response safely.

Goal

Create a GET request in Go that includes custom headers and prints the HTTP status returned by the server.

Requirements

  • Create an http.Client and build a GET request with http.NewRequest
  • Add at least two headers: Authorization and Accept
  • Send the request using client.Do(req)
  • Check for errors when creating and sending the request
  • Close the response body after the request completes
Take Quiz

Keep learning

Related questions

View more