Building with NextJS Pt.3 (Syncing GitHub Issues because Jira is Terrible)
The Inevitable Frontend Collision
In Part 2, I waxed poetic about the boring, beautiful stability of a Go standard library backend. Now, it’s time to pay the Piper. We are back in the land of JavaScript, state management, and the ever-shifting sands of Next.js App Router.
My goal for this phase was simple: build a dashboard to manage Projects and Tasks, and then execute the real reason I’m building this app—syncing GitHub issues directly into my workflow so I don’t have to use another bloated enterprise project management tool.
Shadcn to the Rescue
I stand by my original statement: I am not spending 50% of my time tweaking button padding. Enter shadcn/ui.
If you haven’t used it, shadcn/ui isn’t a component library you install via npm; it’s a collection of beautifully designed React components that you copy and paste (via a CLI) directly into your project. You own the code.
npx shadcn@latest add dialog form input select table card In ten seconds, I had accessible, nicely styled components. The Next.js pages themselves are standard React. I created a main /dashboard to list Projects using cards, and a dynamic /projects/[id] route to show the tasks for a specific project in a clean table.
I hooked these up using the plain fetchClient wrapper I showed off in Part 2. No heavy data-fetching libraries. Just useEffect, useState, and fetch. Is it the most perfectly optimized caching strategy in the world? No. Does it work flawlessly for a single-user task manager? Absolutely.
The Real Work: The GitHub API
Now for the fun part. The premise of this tool is that I manage multiple repositories and want my GitHub issues to sync down as tasks in my app.
First, I had to admit I missed something in my initial “perfect” database schema (I literally predicted this in Part 2). My projects table needed a github_repo column, and my users table needed a place to store a github_token (Personal Access Token) so I don’t get immediately rate-limited by GitHub’s API.
Back to Go for a quick migration:
ALTER TABLE projects ADD COLUMN github_repo TEXT;
ALTER TABLE users ADD COLUMN github_token TEXT; With the schema updated, I wrote a new handler in Go: POST /projects/{id}/sync-github.
The logic is straightforward but satisfying:
- Grab the user’s
github_tokenfrom the database. - Fetch the
github_repostring (e.g.,mohammedeabdelaziz/go-next) from the project. - Make a standard
http.NewRequesttohttps://api.github.com/repos/{repo}/issues. - Parse the JSON response.
The Upsert Dilemma
The tricky part of syncing external data is avoiding duplicates without accidentally destroying local changes. If an issue exists in GitHub, I want to pull it down. But if I’ve already pulled it down and modified its priority locally, I don’t want the sync to overwrite my local priority.
I handled this by looping through the fetched GitHub issues and checking them against the tasks already in the database for that project (matching on github_issue_id).
if existingTask, ok := issueMap[issue.ID]; ok {
// Update existing task (Title, Body, Status)
// ... but leave my custom local Priority alone!
db.UpdateTask(existingTask)
} else {
// Insert brand new task
db.CreateTask(newTask)
} Hooking it up
Back on the frontend, I added a “Settings” modal to the dashboard where I can paste my GitHub Personal Access Token. Then, on the Project Details page, if a project has a linked repository, a shiny new “Sync GitHub Issues” button appears.
You click it. The Next.js client fires a request to the Go backend. Go talks to GitHub, does the deduplication math, updates SQLite, and returns the fresh tasks. React state updates, the table re-renders.
It’s fast. It’s entirely decoupled. And best of all, I now have my GitHub issues sitting right next to my personal to-dos in a UI I actually control.
Next Steps
The app is functionally complete for basic use. I can create projects, manage tasks, and sync my code issues. But remember Rule #1 from Part 1? I am definitely building a mobile app.
In the next part, we’ll see if this “decoupled API” architecture pays off when we try to wrap this exact same backend with a Tauri or Capacitor mobile shell.
Stay tuned.