Back

Step by Step Guide to Building a Harvest API Integration in Go

Aug 15, 20245 minute read

Introduction

Hey there, fellow Go enthusiast! Ready to dive into the world of Harvest API integration? You're in for a treat. We'll be building a sleek, efficient integration that'll have you tracking time like a pro in no time.

Prerequisites

Before we jump in, make sure you've got:

  • Go installed (I know, obvious, right?)
  • A Harvest account with API credentials (if you don't have one, go grab it!)

Setting up the project

Let's kick things off:

mkdir harvest-integration && cd harvest-integration go mod init harvest-integration

Now, let's grab the HTTP client we'll need:

go get github.com/go-resty/resty/v2

Authentication

First things first, let's get that API token. Head over to your Harvest account settings and generate one if you haven't already.

Now, let's set up our HTTP client:

import ( "github.com/go-resty/resty/v2" ) client := resty.New() client.SetHeader("Authorization", "Bearer YOUR_API_TOKEN") client.SetHeader("Harvest-Account-ID", "YOUR_ACCOUNT_ID")

Making API requests

Time to make some magic happen! Let's fetch some projects:

resp, err := client.R(). SetResult(&[]Project{}). Get("https://api.harvestapp.com/v2/projects") if err != nil { log.Fatalf("Error fetching projects: %v", err) } projects := resp.Result().(*[]Project)

Creating a time entry? Easy peasy:

timeEntry := TimeEntry{ ProjectID: 123456, TaskID: 789012, SpentDate: "2023-05-15", Hours: 2.5, } resp, err := client.R(). SetBody(timeEntry). SetResult(&TimeEntry{}). Post("https://api.harvestapp.com/v2/time_entries")

Handling responses

Parsing JSON responses is a breeze with Go:

type Project struct { ID int `json:"id"` Name string `json:"name"` } // ... in your request var projects []Project err := json.Unmarshal(resp.Body(), &projects)

Don't forget to handle those errors like a champ:

if err != nil { if resp.StatusCode() == 429 { log.Println("Whoa there! We're being rate limited. Let's take a breather.") // Implement backoff strategy } else { log.Printf("Oops! Something went wrong: %v", err) } }

Implementing key features

Here's a quick rundown on fetching time entries:

resp, err := client.R(). SetQueryParam("from", "2023-05-01"). SetQueryParam("to", "2023-05-15"). SetResult(&[]TimeEntry{}). Get("https://api.harvestapp.com/v2/time_entries")

Updating an entry? No sweat:

updatedEntry := TimeEntry{Hours: 3.0} resp, err := client.R(). SetBody(updatedEntry). SetResult(&TimeEntry{}). Patch("https://api.harvestapp.com/v2/time_entries/12345")

Optimizing the integration

Keep an eye on those rate limits! Harvest's pretty generous, but let's play nice:

time.Sleep(200 * time.Millisecond) // Simple delay between requests

For frequently accessed data, why not implement some caching?

var projectCache map[int]Project // Populate cache periodically or on-demand

Testing the integration

Let's wrap it up with some testing goodness:

func TestFetchProjects(t *testing.T) { projects, err := FetchProjects() assert.NoError(t, err) assert.NotEmpty(t, projects) }

For integration tests, consider using environment variables for API credentials and a separate test account.

Conclusion

And there you have it! You've just built a rock-solid Harvest API integration in Go. Pretty cool, right? Remember, this is just the beginning. There's a whole world of Harvest API endpoints to explore and integrate.

Keep coding, keep learning, and most importantly, keep tracking that time efficiently! Catch you on the flip side, Gopher!