Back

Reading and Writing Data Using the Harvest API

Aug 15, 20246 minute read

Hey there, fellow JavaScript enthusiasts! Ready to dive into the world of Harvest API integration? Let's roll up our sleeves and get our hands dirty with some code. We'll focus on syncing data for a user-facing integration, so buckle up!

The Harvest API: Your New Best Friend

Harvest's API is a powerful tool for time tracking and project management. We'll be using it to create a seamless integration that'll make your users wonder how they ever lived without it.

Authentication: Getting Past the Bouncer

First things first, we need to get past the velvet rope. Harvest uses OAuth 2.0, so let's set that up:

const axios = require('axios'); async function getAccessToken(code) { const response = await axios.post('https://id.getharvest.com/api/v2/oauth2/token', { code, client_id: YOUR_CLIENT_ID, client_secret: YOUR_CLIENT_SECRET, grant_type: 'authorization_code', }); return response.data.access_token; }

Store that token safely, you'll need it for all your API calls!

Reading Data: Time to Get Nosy

Now that we're in, let's grab some data. Here's how to fetch time entries:

async function getTimeEntries(accessToken, userId, fromDate) { const response = await axios.get('https://api.harvestapp.com/v2/time_entries', { headers: { 'Authorization': `Bearer ${accessToken}`, 'Harvest-Account-Id': YOUR_ACCOUNT_ID, }, params: { user_id: userId, from: fromDate, }, }); return response.data.time_entries; }

Pro tip: Don't forget to handle pagination for large datasets!

Writing Data: Leave Your Mark

Creating a new time entry is just as easy:

async function createTimeEntry(accessToken, projectId, taskId, hours) { const response = await axios.post('https://api.harvestapp.com/v2/time_entries', { project_id: projectId, task_id: taskId, spent_date: new Date().toISOString().split('T')[0], hours, }, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Harvest-Account-Id': YOUR_ACCOUNT_ID, }, }); return response.data; }

Syncing Strategies: Keep It Fresh

Incremental syncing is your friend here. Keep track of the last sync time and only fetch new or updated entries:

let lastSyncTime = localStorage.getItem('lastSyncTime') || new Date(0).toISOString(); async function syncData() { const newEntries = await getTimeEntries(accessToken, userId, lastSyncTime); // Process new entries... lastSyncTime = new Date().toISOString(); localStorage.setItem('lastSyncTime', lastSyncTime); }

Webhooks: Real-time Magic

Set up webhooks to get instant updates. Here's a simple Express handler:

app.post('/webhook', (req, res) => { const event = req.body; if (event.type === 'TimeEntry') { // Handle time entry update updateLocalData(event.data); } res.sendStatus(200); });

Data Transformation: Making It Fit

You might need to massage the data a bit to fit your app's schema:

function transformTimeEntry(harvestEntry) { return { id: harvestEntry.id, duration: harvestEntry.hours * 3600, // Convert to seconds description: harvestEntry.notes, project: { id: harvestEntry.project.id, name: harvestEntry.project.name, }, // ... more fields as needed }; }

Optimization: Speed It Up

Use batch operations when possible, and don't be afraid to make parallel requests:

async function batchUpdate(entries) { const promises = entries.map(entry => updateTimeEntry(accessToken, entry.id, entry)); return Promise.all(promises); }

Testing and Debugging: Trust, but Verify

Always use Harvest's sandbox environment for testing. And mock those API responses in your unit tests:

jest.mock('axios'); test('getTimeEntries fetches data correctly', async () => { axios.get.mockResolvedValue({ data: { time_entries: [/* mock data */] } }); const entries = await getTimeEntries('fake-token', '123', '2023-01-01'); expect(entries).toHaveLength(1); // More assertions... });

Wrapping Up

And there you have it! You're now equipped to create a robust Harvest API integration. Remember, the key is to keep your sync efficient, handle errors gracefully, and always respect API rate limits.

Keep coding, keep learning, and may your time entries always be accurate! 🚀