Skip to content

Commit 18cba75

Browse files
committed
feat: implement caching for GitHub API responses
Add in-memory cache with 5-minute TTL for GitHub GraphQL responses. Duplicate requests for the same username are served from cache, reducing API calls and improving response times. - Add lib/cache.ts with generic get/set/stats helpers - Wrap fetchGitHubUserData with cache lookup - Add Cache-Control header to compare API response Closes #33
1 parent 60c8f15 commit 18cba75

3 files changed

Lines changed: 48 additions & 2 deletions

File tree

app/api/compare/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ export async function GET(request: Request) {
3333
})
3434
);
3535

36-
return NextResponse.json({ success: true, users: results });
36+
return NextResponse.json(
37+
{ success: true, users: results },
38+
{ headers: { "Cache-Control": "public, s-maxage=300, stale-while-revalidate=60" } }
39+
);
3740
} catch (error: any) {
3841
console.error("GitHub score error:", error);
3942
const message =

lib/cache.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
interface CacheEntry<T> {
2+
data: T;
3+
expiresAt: number;
4+
}
5+
6+
const store = new Map<string, CacheEntry<unknown>>();
7+
8+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
9+
10+
export function getCached<T>(key: string): T | undefined {
11+
const entry = store.get(key);
12+
if (!entry) return undefined;
13+
if (Date.now() > entry.expiresAt) {
14+
store.delete(key);
15+
return undefined;
16+
}
17+
return entry.data as T;
18+
}
19+
20+
export function setCached<T>(key: string, data: T, ttlMs = DEFAULT_TTL_MS) {
21+
store.set(key, { data, expiresAt: Date.now() + ttlMs });
22+
}
23+
24+
export function cacheStats() {
25+
let valid = 0;
26+
const now = Date.now();
27+
for (const [key, entry] of store) {
28+
if (now > entry.expiresAt) {
29+
store.delete(key);
30+
} else {
31+
valid++;
32+
}
33+
}
34+
return { size: valid };
35+
}

lib/github.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ContributionTotals, GitHubUserData, PullRequestNode, RepoNode } from "@/types/github";
22
import { graphql } from "@octokit/graphql";
3+
import { getCached, setCached } from "./cache";
34

45
if (!process.env.GITHUB_TOKEN) {
56
throw new Error("Missing GITHUB_TOKEN");
@@ -60,15 +61,22 @@ const QUERY = /* GraphQL */ `
6061
export async function fetchGitHubUserData(
6162
username: string
6263
): Promise<GitHubUserData> {
64+
const cacheKey = `github:${username.toLowerCase()}`;
65+
const cached = getCached<GitHubUserData>(cacheKey);
66+
if (cached) return cached;
67+
6368
const { user } = await client<{ user: any }>(QUERY, { login: username });
6469

6570
if (!user) {
6671
throw new Error("User not found");
6772
}
6873

69-
return {
74+
const data: GitHubUserData = {
7075
repos: user.repositories.nodes as RepoNode[],
7176
pullRequests: user.pullRequests.nodes as PullRequestNode[],
7277
contributions: user.contributionsCollection as ContributionTotals,
7378
};
79+
80+
setCached(cacheKey, data);
81+
return data;
7482
}

0 commit comments

Comments
 (0)