Hey there, fellow JavaScript devs! Ready to dive into the world of GitHub Issues API? Let's get our hands dirty with some code and learn how to build a robust, user-facing integration that syncs data like a champ.
First things first, let's get our environment set up. You'll need a couple of trusty dependencies:
npm install @octokit/rest dotenv
Now, let's authenticate. Create a .env
file and add your GitHub token:
GITHUB_TOKEN=your_token_here
Here's how we'll set up our Octokit instance:
import { Octokit } from "@octokit/rest"; import dotenv from "dotenv"; dotenv.config(); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
Alright, let's grab some issues! Here's a quick example to fetch issues with labels and assignees:
async function getIssues(owner, repo) { const issues = await octokit.paginate(octokit.issues.listForRepo, { owner, repo, state: "all", per_page: 100, }); return issues; }
Pro tip: Always use pagination to handle large datasets and keep an eye on those rate limits!
Creating and updating issues is a breeze. Check this out:
async function createIssue(owner, repo, title, body, labels) { const issue = await octokit.issues.create({ owner, repo, title, body, labels, }); return issue.data; }
Now for the fun part – syncing data between your local database and GitHub Issues. Here's a simple two-way sync function:
async function syncIssues(owner, repo, localIssues) { const githubIssues = await getIssues(owner, repo); for (const localIssue of localIssues) { const githubIssue = githubIssues.find(i => i.title === localIssue.title); if (githubIssue) { // Update existing issue await updateIssue(owner, repo, githubIssue.number, localIssue); } else { // Create new issue await createIssue(owner, repo, localIssue.title, localIssue.body, localIssue.labels); } } // Handle deletions... }
Want real-time updates? Webhooks are your new best friend. Here's a quick Express.js webhook handler:
import express from 'express'; const app = express(); app.post('/webhook', express.json(), (req, res) => { const { action, issue } = req.body; if (action === 'opened' || action === 'edited') { // Update local database with issue data updateLocalIssue(issue); } res.sendStatus(200); });
Let's add some retry logic to our API calls:
async function retryApiCall(apiFunction, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await apiFunction(); } catch (error) { if (i === maxRetries - 1) throw error; await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i))); } } }
Let's implement a simple cache to speed things up:
const cache = new Map(); async function getCachedIssues(owner, repo) { const cacheKey = `${owner}/${repo}`; if (cache.has(cacheKey)) { return cache.get(cacheKey); } const issues = await getIssues(owner, repo); cache.set(cacheKey, issues); return issues; }
Here's a quick Jest test for our sync function:
jest.mock('@octokit/rest'); test('syncIssues updates local issues', async () => { const mockGetIssues = jest.fn().mockResolvedValue([ { number: 1, title: 'Existing Issue', body: 'Old body' } ]); const mockUpdateIssue = jest.fn(); const mockCreateIssue = jest.fn(); Octokit.mockImplementation(() => ({ issues: { listForRepo: mockGetIssues, update: mockUpdateIssue, create: mockCreateIssue, }, })); await syncIssues('owner', 'repo', [ { title: 'Existing Issue', body: 'New body' }, { title: 'New Issue', body: 'New issue body' }, ]); expect(mockUpdateIssue).toHaveBeenCalledWith(expect.objectContaining({ body: 'New body', })); expect(mockCreateIssue).toHaveBeenCalledWith(expect.objectContaining({ title: 'New Issue', })); });
And there you have it! You're now equipped to build a killer GitHub Issues integration. Remember to always respect rate limits, handle errors gracefully, and keep your code clean and testable. Happy coding, and may your pull requests always be approved on the first try! 🚀