DEV Community

Adrish Dey
Adrish Dey

Posted on

I built a tool for you to flex your GitHub stats

Your GitHub profile is a green grid and a list of repo names. It doesn't feel like yours.

So I built DevPrint โ€” log in with GitHub, and it generates a card: your stats, top languages, contribution heatmap, and a shareable link. No forms. No manual input. Your identity, decoded from your own API data.

๐Ÿ”— Live: devprint.adrish.me
๐Ÿ’ป Code: github.com/adrish-mage/devprint

What it does

  • Log in with GitHub โ†’ your card generates instantly, no config
  • Language breakdown, follower/star counts, and a contribution heatmap โ€” all computed server-side
  • Search any GitHub username and generate their card too, no login needed
  • Logged-in users get a permanent shareable link at /u/your-username
  • Zero third-party stats widgets. Every number on the card comes from raw GitHub API responses that I parse myself

Why build another GitHub card generator

Mostly because the existing ones are either static SVG badges or paywalled. I wanted to actually own the data pipeline โ€” auth, caching, rate limits, all of it โ€” rather than embed someone else's widget. It turned into a genuinely useful sandbox for a few things I hadn't touched before: OpenID Connect, GraphQL, and cache design under a real rate limit ceiling.

The stack

Layer Tech
Runtime Node.js 20
Server Express 4
Templating EJS
Auth Auth0 ยท GitHub OAuth (OIDC)
Data GitHub REST v3 + GraphQL v4
Persistence MongoDB (cache-aside, 1hr TTL)
Deploy Render + a Namecheap domain

A few technical bits worth mentioning

Parallel fetching, not sequential. Profile, repos, and the GraphQL contribution calendar all fire together:

const [profileRes, repoData, graphQL] = await Promise.all([
  axios.get(`https://api.github.com/users/${username}`, { headers }),
  axios.get(`https://api.github.com/users/${username}/repos`, { headers }),
  axios.post("https://api.github.com/graphql", buildQuery(username), { headers }),
]);
Enter fullscreen mode Exit fullscreen mode

Cache-aside with a TTL, not a cron job. No background refresh โ€” every read checks freshness first, and only fetches from GitHub on a genuine miss:

async function getGithubData(username) {
  const cached = await Profile.findOne({ username });
  const isFresh = cached && (Date.now() - cached.fetchedAt) < ONE_HOUR;
  if (isFresh) return cached;
  const freshData = await fetchGitHubData(username);
  // ...upsert and return
}
Enter fullscreen mode Exit fullscreen mode

This is the difference between blowing through GitHub's 5000 req/hr ceiling on a traffic spike and not even noticing one.

GraphQL queries as variables, not string interpolation. Early version built the query with template literals directly around the username โ€” worked fine functionally, but it's the kind of thing that looks bad in a code review and is bad practice regardless of actual exploitability. Fixed it to pass login as a proper GraphQL variable instead:

const buildQuery = (login) => ({
  query: `query($login: String!) { user(login: $login) { ... } }`,
  variables: { login }
});
Enter fullscreen mode Exit fullscreen mode

Failed searches don't dead-end. If you search a username that doesn't exist, you land back on your own card with an inline error โ€” not a raw stack trace, not a blank page.

What's still rough

Being honest about it: no per-IP rate limiting yet (the cache absorbs most organic traffic, but a determined bad actor cycling random usernames could still force cache misses), and structured logging is still a roadmap item, not a shipped one. Both are next.

Try it

If you've got a GitHub account, your card is one login away: devprint.adrish.me

I'd genuinely like feedback โ€” on the UI, on the code, on what's missing. Repo's open: github.com/adrish-mage/devprint. Do give a star if you like it ! Issues and PRs welcome.

Top comments (0)