A fast, accurate GitHub API proxy and stats aggregator. Features a GraphQL-powered statistics engine, high-performance LRU caching with TTL, and fully parallel data fetching.
- High-Efficiency Stats β Aggregates profile, repository, and contribution data in a single GraphQL round-trip.
- Intelligent Proxy β Secure access to any public GitHub REST endpoint with configurable caching.
- O(1) LRU Cache β Doubly-linked list + Map implementation with per-entry TTL and hit-rate tracking.
- Accurate Metrics β Computes contribution streaks from the full 365-day contribution calendar.
- Hardened Reliability β Integrated exponential-backoff retry logic and comprehensive error handling.
- GitHub GraphQL stats β profile, repos, languages, commits, PRs, issues, and streaks in one query
- GitHub REST proxy β access any public GitHub endpoint with optional caching
- Smart LRU cache β O(1) get/set, per-entry TTL, hit-rate tracking, automatic eviction
- Accurate streaks β computed from the full contribution calendar (same data as your GitHub profile)
- Retry logic β exponential backoff on 5xx and transient network errors
- Helmet.js security headers
- Configurable CORS
- Rate-limit headers forwarded to the client
- Username validation before hitting GitHub
- Parallel REST fetches with
Promise.allSettled(partial failure doesn't kill the whole request)
- Local Express server
- Netlify Functions compatible
- Graceful shutdown (SIGTERM/SIGINT)
Client App
β
βΌ
Express Server (server.ts)
β
βββ /api/github/v2/stats βββΊ GitHub GraphQL API (1 request)
β β β
β βββββββββββββββββββ LRU Stats Cache (6h TTL)
β
βββ /api/github/v2 βββΊ GitHub REST API
β β
βββββββββββββββββββ LRU General Cache (14d TTL)
File structure:
src/
βββ server.ts # Express app + startup
βββ routes/
β βββ github.ts # All GitHub routes + GraphQL logic
βββ cache/
β βββ lruCache.ts # LRU cache implementation
βββ middleware/
βββ requestLogger.ts
βββ errorHandler.ts
types.ts # Shared TypeScript interfaces
- Node.js 20+
- GitHub Personal Access Token (required for GraphQL β see note below)
Note: The GraphQL API (
api.github.com/graphql) requires authentication. Without a token the stats endpoint will return 401. The REST proxy endpoint works unauthenticated but is limited to 60 req/hr.
git clone https://github.com/amitxd75/github-api-backend.git
cd github-api-backend
npm install
cp .env.example .env
# Add your GITHUB_TOKEN to .env# Required for /stats (GraphQL needs auth)
GITHUB_TOKEN=ghp_your_token_here
# Comma-separated allowed CORS origins
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com
NODE_ENV=development
PORT=3001Get a token at github.com/settings/tokens β only read:user and public_repo scopes are needed.
npm run dev # development (hot reload)
npm run build # compile TypeScript
npm start # productionAll endpoints are identical to v2 β no client changes required.
- Local:
http://localhost:3001 - Netlify:
https://your-site.netlify.app/.netlify/functions/api
Server status, uptime, memory usage, token presence.
API documentation and available endpoints.
Proxy any GitHub REST API endpoint.
| Parameter | Required | Description |
|---|---|---|
endpoint |
β | GitHub API path, must start with / |
cache |
β | Set to true to cache for 14 days |
Examples:
# User profile
curl "http://localhost:3001/api/github/v2?endpoint=/users/octocat"
# Repos with caching
curl "http://localhost:3001/api/github/v2?endpoint=/users/octocat/repos&cache=true"Fetch comprehensive stats for a GitHub user. Powered by GraphQL β one request to GitHub, cached for 6 hours.
| Parameter | Required | Description |
|---|---|---|
username |
β | GitHub username |
force |
β | true bypasses cache |
Response:
{
"username": "amitxd75",
"name": "Amit",
"avatarUrl": "https://avatars.githubusercontent.com/...",
"bio": "...",
"location": "...",
"company": null,
"websiteUrl": "https://...",
"twitterUsername": null,
"followers": 42,
"following": 15,
"publicRepos": 25,
"publicGists": 3,
"accountCreated": "2020-01-01T00:00:00Z",
"lastActivity": "2024-06-01T12:00:00Z",
"totalRepos": 25,
"totalStars": 150,
"totalForks": 30,
"contributedTo": 5,
"totalCommits": 1240,
"totalPRs": 48,
"totalIssues": 22,
"currentStreak": 7,
"longestStreak": 30,
"topLanguages": {
"TypeScript": 45,
"JavaScript": 30,
"Python": 15,
"Go": 10
},
"recentRepoActivity": 3,
"lastUpdated": "2024-06-01T12:00:00Z",
"cacheAge": 3600
}
cacheAgeis only present on cached responses (seconds since last fetch).totalCommits,totalPRs,totalIssuesreflect the current contribution year (resets Jan 1).
Cache health and hit-rate metrics.
{
"general": {
"size": 12,
"capacity": 1000,
"hits": 340,
"misses": 22,
"evictions": 0,
"hitRate": "93.9%",
"ttl": "14 days"
},
"stats": {
"size": 3,
"capacity": 200,
"hits": 87,
"misses": 3,
"evictions": 0,
"hitRate": "96.7%",
"ttl": "6 hours",
"keys": ["stats_amitxd75"]
}
}Clear all cache entries.
Clear a specific entry (e.g. DELETE /api/github/v2/cache/amitxd75 or by endpoint path).
// Fetch comprehensive statistics
const res = await fetch('/api/github/v2/stats?username=amitxd75');
const stats = await res.json();
console.log(stats.totalCommits); // now accurate (full year via GraphQL)
console.log(stats.currentStreak); // now accurate (365-day calendar)
console.log(stats.avatarUrl); // new field available
console.log(stats.name); // new field availableimport { useState, useEffect } from 'react';
function useGitHubStats(username) {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!username) return;
setLoading(true);
fetch(`/api/github/v2/stats?username=${username}`)
.then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
.then(data => { setStats(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, [username]);
return { stats, loading, error };
}
function GitHubProfile({ username }) {
const { stats, loading, error } = useGitHubStats(username);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<img src={stats.avatarUrl} alt={stats.name} />
<h2>{stats.name ?? stats.username}</h2>
<p>β {stats.totalStars} stars Β· π₯ {stats.currentStreak} day streak</p>
</div>
);
}import requests
def get_github_stats(username, base_url="http://localhost:3001"):
r = requests.get(f"{base_url}/api/github/v2/stats", params={"username": username})
r.raise_for_status()
return r.json()
stats = get_github_stats("amitxd75")
print(f"{stats['totalCommits']} commits Β· {stats['currentStreak']} day streak")| Variable | Required | Default | Description |
|---|---|---|---|
GITHUB_TOKEN |
Yes for /stats | β | GitHub PAT β needs read:user, public_repo |
NODE_ENV |
No | development |
development or production |
ALLOWED_ORIGINS |
No | http://localhost:3000 |
Comma-separated CORS origins |
PORT |
No | 3001 |
Local server port |
| Cache | TTL | Capacity |
|---|---|---|
| General (REST proxy) | 14 days | 1,000 entries |
| Stats (GraphQL) | 6 hours | 200 entries |
netlify.toml:
[build]
functions = "netlify/functions"
[functions]
directory = "netlify/functions"
node_bundler = "esbuild"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/api/:splat"
status = 200
[[redirects]]
from = "/health"
to = "/.netlify/functions/api/health"
status = 200
[build.environment]
NODE_VERSION = "22"Environment variables to set in Netlify UI:
GITHUB_TOKEN=ghp_...
NODE_ENV=production
ALLOWED_ORIGINS=https://yourdomain.com
Heads up on serverless caching: Netlify Functions are stateless β the LRU cache resets on each cold start. For production with high traffic, consider replacing the in-memory cache with Redis (Upstash has a generous free tier). For a portfolio/low-traffic site the current setup is fine.
GraphQL requires authentication. Add GITHUB_TOKEN to your .env.
Rate limit hit (60/hr without token, 5000/hr with). Add GITHUB_TOKEN.
Add your domain to ALLOWED_ORIGINS.
Your token may lack read:user scope, or the account has no public contributions this year.
git clone https://github.com/amitxd75/github-api-backend.git
npm install
npm run dev- Fork β feature branch β PR
- Run
npm run buildto check types before submitting
MIT β see LICENSE.
β Star this repo if it helped you!
Made with β€οΈ by amitxd75