npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nuwan-dev/github-stats

v2.2.0

Published

A TypeScript library to fetch GitHub user stats (contributions, languages, repo rankings) with optional React components.

Readme

@nuwan-dev/github-stats

A powerful TypeScript library for GitHub analytics - fetch contribution calendars, analyze language statistics, and build beautiful developer profiles.

npm version License: MIT TypeScript

Build comprehensive GitHub profiles with contribution graphs and programming language insights. Perfect for portfolios, dashboards, and developer analytics.


✨ Features

  • 📅 Contribution Calendars - Beautiful GitHub-style contribution graphs with React components
  • 💻 Language Statistics - Complete language analysis across ALL repositories (public + private)
  • 🔒 Private Repository Support - Full access to private repos with proper token scopes
  • 📊 Accurate Data - Fetches ALL languages (not just top 20) with comprehensive pagination
  • High Performance - GraphQL-based API with smart pagination
  • 🎯 Type-Safe - Full TypeScript support with comprehensive type definitions
  • 🎨 Customizable - Flexible options for forks, private repos, and styling
  • 🚀 Framework Agnostic - Works with Next.js, Vite, React, and any TypeScript project

📦 Installation

npm install @nuwan-dev/github-stats

🚀 Quick Start

1. Get a GitHub Token

You need a GitHub Personal Access Token to access GitHub's API:

  1. Go to GitHub Settings → Tokens
  2. Click "Generate new token (classic)" or create a Fine-grained token
  3. Required scopes:
    • read:user - For basic user information
    • repo - Required for private repository access

Important: Without the repo scope, private repositories will not be included in language statistics.

  1. Save your token securely in .env.local:
# For server-side usage (Next.js Server Components, Node.js)
GITHUB_TOKEN=ghp_your_token_here

# For client-side usage (Next.js Client Components)
NEXT_PUBLIC_GITHUB_TOKEN=ghp_your_token_here

Security Note: Never commit tokens to git. The .env.local file is automatically ignored by Next.js.

2. Choose Your Feature

import {
  ContributionGraph, // React component for graphs
  fetchContributionCalendar, // API for contribution data
  fetchLanguageStats, // API for language analysis
  getTopLanguages, // Helper for top N languages
} from "@nuwan-dev/github-stats";

// For React components
import "@nuwan-dev/github-stats/style.css";

📊 Feature 1: Contribution Calendars

Beautiful, GitHub-style contribution graphs as React components.

Auto-Fetch Mode (Easiest)

import { ContributionGraph } from "@nuwan-dev/github-stats";
import "@nuwan-dev/github-stats/style.css";

export default function Page() {
  return (
    <ContributionGraph
      username="nuwandev"
      githubToken={process.env.GITHUB_TOKEN}
    />
  );
}

Manual Fetch Mode (More Control)

import {
  fetchContributionCalendar,
  ContributionGraph,
} from "@nuwan-dev/github-stats";

const calendar = await fetchContributionCalendar("nuwandev", token);

<ContributionGraph
  calendar={calendar}
  yearLabel="2025"
  totalLabel="Keep coding! 🚀"
/>;

Custom Date Ranges

const lastQuarter = await fetchContributionCalendar(
  "nuwandev",
  token,
  new Date("2024-10-01"),
  new Date("2024-12-31"),
);

<ContributionGraph calendar={lastQuarter} yearLabel="Q4 2024" />;

API Reference

<ContributionGraph /> Props

| Prop | Type | Required | Description | | ------------- | ---------------------- | --------------- | ---------------------- | | username | string | ✅ (auto-fetch) | GitHub username | | githubToken | string | ✅ (auto-fetch) | GitHub token | | calendar | ContributionCalendar | ✅ (manual) | Calendar data | | yearLabel | string \| number | ❌ | Custom header label | | totalLabel | string | ❌ | Custom footer text | | className | string | ❌ | Additional CSS classes |

fetchContributionCalendar()

fetchContributionCalendar(
  username: string,
  token: string,
  start?: Date,    // Default: 1 year ago
  end?: Date       // Default: today
): Promise<ContributionCalendar>

Returns:

interface ContributionCalendar {
  weeks: ContributionWeek[];
  total: number;
}

interface ContributionDay {
  date: string; // ISO format: "2025-01-23"
  count: number; // Number of contributions
  level: number; // 0-4 (color intensity)
  color?: string; // GitHub's color
}

🎯 Feature 2: Language Statistics

Analyze programming languages across ALL your GitHub repositories with complete accuracy.

Key Features

Complete Coverage - Fetches ALL repositories (public + private) and ALL languages (no 20-item limit)
Private Repository Support - Access private repos with proper token scopes
Flexible Filtering - Choose to include/exclude forks and private repos
Accurate Percentages - Based on total codebase size across all repos

Basic Usage

import { fetchLanguageStats } from "@nuwan-dev/github-stats";

// Fetch all public repos, excluding forks (default)
const stats = await fetchLanguageStats("nuwandev", token);

console.log(stats);
// {
//   languages: [
//     {
//       name: 'TypeScript',
//       size: 1234567,          // Bytes of code
//       repoCount: 42,           // Number of repos using this
//       percentage: 45.2,        // Percentage of total codebase
//       color: '#3178c6'         // GitHub's official color
//     },
//     { name: 'JavaScript', size: 987654, repoCount: 38, percentage: 36.1, ... },
//     ...
//   ],
//   totalSize: 2734567,         // Total bytes across all languages
//   totalRepos: 45              // Total repositories analyzed
// }

Include Private Repositories

// Fetch ALL repos including private ones
const allStats = await fetchLanguageStats("nuwandev", token, {
  includePrivate: true, // Access private repos (requires 'repo' scope)
  includeForks: false, // Still exclude forks
});

console.log(`Analyzed ${allStats.totalRepos} repos (including private)`);

Include Forks

// Include forked repositories in analysis
const statsWithForks = await fetchLanguageStats("nuwandev", token, {
  includePrivate: false,
  includeForks: true, // Include forks
});

All Options

const completeStats = await fetchLanguageStats("nuwandev", token, {
  includePrivate: true, // Default: false
  includeForks: true, // Default: false
});

Get Top Languages

import { getTopLanguages } from "@nuwan-dev/github-stats";

// Get top 5 languages
const topSkills = await getTopLanguages("nuwandev", token, 5);

topSkills.forEach((skill, i) => {
  console.log(`${i + 1}. ${skill.name} - ${skill.percentage.toFixed(1)}%`);
});
// 1. TypeScript - 45.2%
// 2. JavaScript - 36.1%
// 3. Python - 16.7%
// 4. Go - 1.5%
// 5. Rust - 0.5%

Build a Skills Profile

const stats = await fetchLanguageStats("username", token, {
  includePrivate: true,
});

const profile = {
  // Most used language
  primarySkill: stats.languages[0],

  // Expert level (>15% of codebase)
  expertise: stats.languages.filter((l) => l.percentage > 15),

  // Proficient (5-15%)
  proficient: stats.languages.filter(
    (l) => l.percentage >= 5 && l.percentage <= 15,
  ),

  // Familiar (<5%)
  familiar: stats.languages.filter((l) => l.percentage < 5),

  // Metrics
  diversity: stats.languages.length,
  totalProjects: stats.totalRepos,
  totalCodeSize: stats.totalSize,
};

Language Statistics API Reference

fetchLanguageStats()

fetchLanguageStats(
  username: string,
  token: string,
  options?: {
    includePrivate?: boolean;  // Default: false - Include private repos (requires 'repo' scope)
    includeForks?: boolean;    // Default: false - Include forked repos
  }
): Promise<LanguageStatsResult>

Returns:

interface LanguageStatsResult {
  languages: LanguageStats[]; // Sorted by percentage (high to low)
  totalSize: number; // Total bytes across all languages
  totalRepos: number; // Repositories analyzed
}

interface LanguageStats {
  name: string; // "TypeScript", "JavaScript", etc.
  size: number; // Total bytes of code
  repoCount: number; // Number of repos using this language
  percentage: number; // Percentage of total codebase (0-100)
  color?: string; // GitHub's official color (#hex)
}

getTopLanguages()

getTopLanguages(
  username: string,
  token: string,
  limit?: number,  // Default: 10
  options?: {
    includePrivate?: boolean;
    includeForks?: boolean;
  }
): Promise<LanguageStats[]>

🎨 Complete Examples

Developer Portfolio Dashboard

import {
  fetchContributionCalendar,
  fetchLanguageStats,
  ContributionGraph,
} from "@nuwan-dev/github-stats";
import "@nuwan-dev/github-stats/style.css";

export default async function Portfolio() {
  const username = "nuwandev";
  const token = process.env.GITHUB_TOKEN!;

  // Fetch all data in parallel
  const [contributions, languages] = await Promise.all([
    fetchContributionCalendar(username, token),
    fetchLanguageStats(username, token, {
      includePrivate: true,
      includeForks: false,
    }),
  ]);

  return (
    <div className="space-y-8">
      {/* Profile Header */}
      <div>
        <h1>@{username}</h1>
        <p>{contributions.total} contributions this year</p>
        <p>Primary language: {languages.languages[0]?.name}</p>
      </div>

      {/* Contribution Graph */}
      <ContributionGraph
        calendar={contributions}
        yearLabel={new Date().getFullYear()}
      />

      {/* Top Skills */}
      <div>
        <h2>Top Skills</h2>
        {languages.languages.slice(0, 5).map((lang) => (
          <div key={lang.name}>
            <span>{lang.name}</span>
            <span>{lang.percentage.toFixed(1)}%</span>
            <div
              className="h-2 rounded-full"
              style={{
                width: `${lang.percentage}%`,
                backgroundColor: lang.color,
              }}
            />
          </div>
        ))}
      </div>

      {/* Stats Grid */}
      <div className="grid grid-cols-3 gap-4">
        <div>
          <h3>Total Repos</h3>
          <p>{languages.totalRepos}</p>
        </div>
        <div>
          <h3>Languages</h3>
          <p>{languages.languages.length}</p>
        </div>
        <div>
          <h3>Code Size</h3>
          <p>{(languages.totalSize / 1024 / 1024).toFixed(2)} MB</p>
        </div>
      </div>
    </div>
  );
}

Client-Side Usage (Next.js App Router)

"use client";

import { useEffect, useState } from "react";
import {
  fetchLanguageStats,
  ContributionGraph,
  type LanguageStatsResult,
} from "@nuwan-dev/github-stats";

export default function StatsPage() {
  const [languages, setLanguages] = useState<LanguageStatsResult | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadStats() {
      const token = process.env.NEXT_PUBLIC_GITHUB_TOKEN!;
      const stats = await fetchLanguageStats("nuwandev", token, {
        includePrivate: true,
      });
      setLanguages(stats);
      setLoading(false);
    }
    loadStats();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <h1>My GitHub Stats</h1>
      {languages && (
        <div>
          {languages.languages.map((lang) => (
            <div key={lang.name}>
              {lang.name}: {lang.percentage.toFixed(1)}%
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export function SkillsChart({ username, token }) { const [stats, setStats] = useState(null);

useEffect(() => { fetchLanguageStats(username, token).then(setStats); }, [username, token]);

if (!stats) return Loading skills...;

return (

  {/* Pie chart or bar chart */}
  {stats.languages.map((lang) => (
    <div key={lang.language} className="flex items-center gap-4">
      <div
        className="w-4 h-4 rounded"
        style={{ backgroundColor: lang.color }}
      />
      <span className="flex-1">{lang.language}</span>
      <span className="font-mono">{lang.percentage.toFixed(1)}%</span>
      <span className="text-sm text-gray-500">{lang.repos} repos</span>
    </div>
  ))}

  <div className="text-sm text-gray-600">
    Total: {stats.totalRepos} repositories, {stats.languages.length}{" "}
    languages
  </div>
</div>

); }


### API Route (Next.js)

```typescript
// app/api/github-stats/route.ts
import {
  fetchContributionCalendar,
  fetchLanguageStats,
} from "@nuwan-dev/github-stats";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const username = searchParams.get("username");

  if (!username) {
    return NextResponse.json({ error: "Username required" }, { status: 400 });
  }

  try {
    const token = process.env.GITHUB_TOKEN!;

    const [contributions, languages] = await Promise.all([
      fetchContributionCalendar(username, token),
      fetchLanguageStats(username, token),
    ]);

    return NextResponse.json({
      username,
      contributions: {
        total: contributions.total,
        weeks: contributions.weeks.length,
      },
      languages: {
        primary: languages.languages[0],
        top5: languages.languages.slice(0, 5),
        total: languages.languages.length,
      },
    });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to fetch GitHub stats" },
      { status: 500 },
    );
  }
}

🎯 Framework Integration

Next.js App Router (Server Component)

// app/page.tsx
import "@nuwan-dev/github-stats/style.css";
import { ContributionGraph, fetchLanguageStats } from "@nuwan-dev/github-stats";

export default async function Page() {
  const stats = await fetchLanguageStats("nuwandev", process.env.GITHUB_TOKEN!);

  return (
    <main>
      <ContributionGraph
        username="nuwandev"
        githubToken={process.env.GITHUB_TOKEN!}
      />

      <div className="mt-8">
        <h2>Primary Skills</h2>
        {stats.languages.slice(0, 5).map((lang) => (
          <div key={lang.language}>
            {lang.language}: {lang.percentage.toFixed(1)}%
          </div>
        ))}
      </div>
    </main>
  );
}

Next.js Pages Router (SSR)

// pages/profile.tsx
import type { GetServerSideProps } from "next";
import {
  fetchContributionCalendar,
  fetchLanguageStats,
} from "@nuwan-dev/github-stats";

export const getServerSideProps: GetServerSideProps = async () => {
  const token = process.env.GITHUB_TOKEN!;
  const [contributions, languages] = await Promise.all([
    fetchContributionCalendar("nuwandev", token),
    fetchLanguageStats("nuwandev", token),
  ]);

  return { props: { contributions, languages } };
};

export default function Profile({ contributions, languages }) {
  return (
    <div>
      <h1>GitHub Profile</h1>
      <p>{contributions.total} contributions</p>
      <p>Top language: {languages.languages[0]?.language}</p>
    </div>
  );
}

Vite + React (Client-Side)

// src/App.tsx
import { useEffect, useState } from "react";
import { fetchLanguageStats, ContributionGraph } from "@nuwan-dev/github-stats";
import "@nuwan-dev/github-stats/style.css";

export default function App() {
  const [stats, setStats] = useState(null);

  useEffect(() => {
    fetchLanguageStats("nuwandev", import.meta.env.VITE_GITHUB_TOKEN)
      .then(setStats)
      .catch(console.error);
  }, []);

  return (
    <div>
      <ContributionGraph
        username="nuwandev"
        githubToken={import.meta.env.VITE_GITHUB_TOKEN}
      />

      {stats && (
        <div className="mt-8">
          <h2>Skills</h2>
          {stats.languages.slice(0, 5).map((lang) => (
            <div key={lang.language}>{lang.language}</div>
          ))}
        </div>
      )}
    </div>
  );
}

🔒 Security Best Practices

✅ DO

  • Store tokens in environment variables (.env.local)
  • Use server-side rendering (Next.js App Router, SSR)
  • Create API routes to proxy GitHub requests
  • Use server-only tokens (GITHUB_TOKEN, not NEXT_PUBLIC_*)

❌ DON'T

  • Hardcode tokens in source code
  • Expose tokens in client-side JavaScript
  • Commit tokens to version control
  • Use NEXT_PUBLIC_ prefix for GitHub tokens

Example: Secure API Route

// app/api/stats/route.ts - Server only
import { fetchLanguageStats } from "@nuwan-dev/github-stats";

export async function GET(request: Request) {
  const token = process.env.GITHUB_TOKEN; // Server-side only
  const { searchParams } = new URL(request.url);
  const username = searchParams.get("username");

  const stats = await fetchLanguageStats(username!, token!);
  return Response.json(stats);
}
// app/page.tsx - Client
"use client";

async function getStats(username: string) {
  const res = await fetch(`/api/stats?username=${username}`);
  return res.json();
}

⚙️ Advanced Configuration

Custom Contribution Colors

The library uses GitHub's official color palette. To customize:

/* Override in your global CSS */
.contribution-day[data-level="0"] {
  background-color: #161b22;
}
.contribution-day[data-level="1"] {
  background-color: #0e4429;
}
.contribution-day[data-level="2"] {
  background-color: #006d32;
}
.contribution-day[data-level="3"] {
  background-color: #26a641;
}
.contribution-day[data-level="4"] {
  background-color: #39d353;
}

Caching Language Statistics

Language stats don't change frequently. Consider caching:

// Example with Redis
import { fetchLanguageStats } from "@nuwan-dev/github-stats";
import redis from "./redis-client";

async function getCachedLanguageStats(username: string, token: string) {
  const cacheKey = `github:lang:${username}`;

  // Try cache first
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // Fetch fresh data
  const stats = await fetchLanguageStats(username, token);

  // Cache for 24 hours
  await redis.setex(cacheKey, 3600 * 24, JSON.stringify(stats));

  return stats;
}

Rate Limiting

GitHub's GraphQL API has a 5,000 cost points per hour limit:

  • Contribution calendar: ~1 point per request
  • Language statistics: ~1-5 points per page (~3-5 pages for 300 repos)

You can safely fetch stats for hundreds of users per hour.


🔧 Troubleshooting

"Missing data or credentials"

Ensure you're providing the required props:

// ❌ Wrong - missing token
<ContributionGraph username="nuwandev" />

// ✅ Correct - auto-fetch mode
<ContributionGraph username="nuwandev" githubToken={token} />

// ✅ Correct - manual mode
<ContributionGraph calendar={calendarData} />

Styles not appearing

Import the CSS file:

import "@nuwan-dev/github-stats/style.css";

"User not found or has no repositories"

The user may:

  • Not exist
  • Have no public repositories
  • Have only forked repositories (these are excluded)
  • Have invalid GitHub token

TypeScript Errors

Make sure you're importing types:

import type {
  ContributionCalendar,
  LanguageStats,
  LanguageStatsResult,
} from "@nuwan-dev/github-stats";

📊 How It Works

Contribution Calendar

  • Uses GitHub's GraphQL API
  • Fetches contribution data for specified date range
  • Maps contribution counts to color levels (0-4)
  • Renders as React component with GitHub-style grid

Language Statistics

  • Queries all user repositories (excludes forks)
  • Fetches up to 20 languages per repository
  • Aggregates by bytes of code (not repo count)
  • Calculates percentages across total codebase
  • Same algorithm as GitHub's profile language graph

Why bytes, not repo count?

  • More accurate representation of actual work
  • A 1M-line TypeScript project weighs more than a 10-line shell script
  • Matches GitHub's official methodology

📚 TypeScript Support

Full type definitions included:

import type {
  // Contribution types
  ContributionCalendar,
  ContributionWeek,
  ContributionDay,
  ContributionGraphProps,

  // Language types
  LanguageStats,
  LanguageStatsResult,
} from "@nuwan-dev/github-stats";

🗺️ Roadmap

  • [ ] Organization statistics
  • [ ] Pull request metrics
  • [ ] Issue tracking analytics
  • [ ] Commit frequency analysis
  • [ ] Repository star history
  • [ ] Follower/following insights
  • [ ] Custom graph themes
  • [ ] Export to PNG/SVG

🤝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License – see the LICENSE file for details.


🔗 Links


🙏 Acknowledgments

  • GitHub's GraphQL API for powerful data access
  • The open-source community for inspiration and feedback

Made with ❤️ by nuwan-dev

If you find this library useful, please consider giving it a ⭐ on GitHub!