Creating an API Client in Go

When consuming a REST API it's a good practice to write an API Client (API Wrapper) for yourself or your own REST API, so other users could more easily access it.

Fortunately, Go provides everything we need out of the box. The net/http library combined with other Go base libraries is all we need to write our own API Clients.

The Basics

Let's start with the basics. The package should be the named after the API that is being wrapped (such as twitter, slack or scaledrone). We'll call it myservice in this example.

One of the most popular methods of REST API authentication is Basic Authentication. We'll ask the user for a username and password.

package myservice
 
const baseURL string = "https://www.myservice.com/v1"
 
type Client struct {
	Username string
	Password string
}
 
func NewBasicAuthClient(username, password string) *Client {
	return &Client{
		Username: username,
		Password: password,
	}
}

POST request

Myservice has an imaginary endpoint POST /:username/todos for creating todos.

Since Go doesn't have template strings can use fmt.Sprintf instead. Next, let's create the request and use the doRequest function to execute it.

type Todo struct {
	ID      int    `json:"id"`
	Content string `json:"content"`
	Done    bool   `json:"done"`
}
 
func (s *Client) AddTodo(todo *Todo) error {
	url := fmt.Sprintf(baseURL+"/%s/todos", s.Username)
	fmt.Println(url)
	j, err := json.Marshal(todo)
	if err != nil {
		return err
	}
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(j))
	if err != nil {
		return err
	}
	_, err = s.doRequest(req)
	return err
}

Sending the request

The doRequest function will take a http.Request object, set the Authentication header and then run the HTTP request.

func (s *Client) doRequest(req *http.Request) ([]byte, error) {
	req.SetBasicAuth(s.Username, s.Password)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if 200 != resp.StatusCode {
		return nil, fmt.Errorf("%s", body)
	}
	return body, nil
}

GET request

The GET request works similarly to the POST request, but now we also need to Unmarshal the response into a struct.

func (s *Client) GetTodo(id int) (*Todo, error) {
	url := fmt.Sprintf(baseURL+"/%s/todos/%d", s.Username, id)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	bytes, err := s.doRequest(req)
	if err != nil {
		return nil, err
	}
	var data Todo
	err = json.Unmarshal(bytes, &data)
	if err != nil {
		return nil, err
	}
	return &data, nil
}

You are done 🎉. Let's try the API Client out in action!

Putting the API Client to use

After you have published your API Client to git you can import and use it like so.

package main
 
import "github.com/username/myservice"
 
func main() {
	client := myservice.NewBasicAuthClient("username", "password")
	todo := Todo{
		Content: "New Todo",
		Done:    true,
	}
	// Add a todo
	_ := client.AddTodo(&todo)
 
	// Fetch a todo
	t, _ := client.GetTodo(1)
}

If you have any questions or feedback, please get in touch.