A "Spotify Wrapped"-style experience for your GitHub profile. Generate personalized year-in-review summaries featuring your coding stats, AI-powered roasts, predictions, and more!
- 📊 Comprehensive Stats - Total commits, active days, contributions, and activity patterns
- 💻 Language Breakdown - Your top programming languages with percentage analysis
- 🏆 Top Repositories - Your most starred and most active repos
- 👻 Ghosted Repos - Identify projects you've abandoned (we've all been there!)
- 🔥 AI-Powered Roasts - Witty, personalized roasts based on your coding habits
- 🔮 Predictions - AI predictions for your next year in tech
- 💡 Developer Advice - Personalized tips based on your GitHub activity
- 📖 Dev Story - A narrative of your year in code
- 📸 Shareable Cards - Beautiful carousel slides to share on social media
This project uses a monorepo structure with:
wrapped/
├── backend/ # Express.js API server
│ ├── config/ # Environment configuration
│ ├── db/ # MongoDB connection management
│ ├── models/ # Mongoose schemas
│ ├── routes/ # API routes
│ ├── services/ # AI service integrations
│ └── utils/ # GitHub API & stats utilities
├── client/ # React + Vite frontend
│ └── src/
│ ├── components/ # UI components
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── views/ # View components
│ │ └── wrapped/ # Carousel slide components
│ ├── lib/ # Utility functions
│ └── types/ # TypeScript types
└── Dockerfile # Multi-stage production build
- Node.js v24+ (LTS recommended)
- MongoDB - Local instance or cloud (MongoDB Atlas)
- GitHub Token - For API access (create one here)
- OpenAI API Key - For AI features (get one here)
-
Clone the repository:
git clone https://github.com/siyabuilds/wrapped.git cd wrapped -
Create a
.envfile in the root directory:# Environment NODE_ENV=development # Backend Server PORT=3001 # Database MONGODB_URI=mongodb://localhost:27017/wrapped_dev # API Keys OPENAI_TOKEN=your_openai_api_key_here GITHUB_TOKEN=your_github_token_here # CORS (for development) CLIENT_URI=http://localhost:5173
cd backend
npm install
npm run dev # Starts with nodemon for hot reloadThe API will be available at http://localhost:3001
cd client
npm install
npm run dev # Starts Vite dev serverThe frontend will be available at http://localhost:5173
The project includes a multi-stage Dockerfile that builds both frontend and backend:
docker build -t github-wrapped .
docker run -p 3001:3001 \
-e MONGODB_URI=your_mongo_uri \
-e OPENAI_TOKEN=your_openai_key \
-e GITHUB_TOKEN=your_github_token \
github-wrapped| Method | Endpoint | Description |
|---|---|---|
GET |
/:username |
Fetch GitHub stats for a user (cached) |
POST |
/:username/roast |
Generate AI roast based on stats |
POST |
/:username/ai-insights |
Generate predictions, advice & dev story |
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
HTML health dashboard |
GET |
/health/json |
JSON health status |
Fetches comprehensive GitHub statistics for the specified user.
Response:
{
"username": "octocat",
"year": 2025,
"avatarUrl": "https://...",
"name": "The Octocat",
"topLanguage": "JavaScript",
"languagesBreakdown": [
{ "name": "JavaScript", "count": 15, "percentage": 45 }
],
"totalCommits": 523,
"activeDays": 187,
"mostActiveDay": "Tuesday",
"ghostedRepo": { "name": "old-project", "ghostDays": 365 },
"topStarredRepo": { "name": "popular-repo", "stars": 1234 },
"totalContributions": 892,
"followers": 1500,
"following": 42
}Generates a witty AI-powered roast.
Request Body:
{
"stats": {
/* WrappedStats object */
}
}Response:
{
"roast": "You wrote 523 commits in JavaScript this year, but let's be honest – half of them were 'fixed typo' 🔥"
}Generates predictions, advice, and developer story.
Request Body:
{
"stats": {
/* WrappedStats object */
}
}Response:
{
"predictions": {
"languagePrediction": "TypeScript might become your new best friend in 2026! 🚀",
"ossPrediction": "You're showing signs of an open source contributor in the making!",
"burnoutRisk": "Your weekend activity suggests healthy work-life balance 💪"
},
"advice": "Consider contributing to larger open source projects...",
"devStory": "2025 was the year you truly found your groove..."
}- Express.js: Lightweight, flexible, and well-suited for REST APIs. The async/await pattern works seamlessly with GitHub and OpenAI API calls.
- MongoDB: Schema-less design allows flexibility for storing varied GitHub stats and AI-generated content. Perfect for caching user data.
- Mongoose: Provides schema validation, indexing, and static methods for clean data access patterns.
- Vite: Blazing fast HMR (Hot Module Replacement) and optimized production builds.
- TypeScript: Type safety for complex stats objects and API responses.
- shadcn/ui: Accessible, customizable components built on Radix UI. Not a dependency—components are copied into the project for full control.
- Tailwind CSS v4: Utility-first styling with the latest CSS features.
The application implements a database-first caching approach:
- When a user requests their wrapped, check MongoDB first
- If cached data exists for the username + year combination, return it immediately
- On cache miss, fetch from GitHub APIs, calculate stats, and cache the result
- AI-generated content (roasts, predictions) is cached separately and lazily generated
Why this approach?
- Reduces GitHub API rate limit hits
- Expensive AI calls are only made once per user/year
- Users see instant results on repeat visits
- Compound index on
(username, year)ensures O(1) lookups
The app uses multiple GitHub APIs to build a comprehensive picture:
| API | Data Retrieved |
|---|---|
REST /users/:username |
Profile info, followers, public repo count |
REST /users/:username/repos |
Repository list with languages, stars, dates |
REST /search/commits |
Commit search for the specific year |
GraphQL contributionsCollection |
Detailed contribution calendar, total counts |
Why combine REST and GraphQL?
- REST is simpler for basic data retrieval
- GraphQL's
contributionsCollectionprovides the contribution calendar that REST doesn't expose - Parallel requests with
Promise.all()minimize latency
- Model:
gpt-4o-mini- Cost-effective while maintaining quality - System Prompts: Carefully crafted to ensure fun, appropriate roasts
- Fallbacks: If OpenAI is unavailable, generic fallback responses are provided
- Structured Output: AI returns JSON for predictions, ensuring consistent parsing
year: () => {
const now = new Date();
const currentYear = now.getFullYear();
const december1st = new Date(currentYear, 11, 1);
return now < december1st ? currentYear - 1 : currentYear;
};Why December 1st?
- Before December, users see last year's wrapped (the year with complete data)
- Starting December 1st, the current year becomes available
- Mirrors how Spotify Wrapped works
The server implements proper signal handling:
- Catches
SIGTERMandSIGINTsignals - Stops accepting new requests
- Waits for existing requests to complete
- Cleanly disconnects from MongoDB
- Forces shutdown after 10-second timeout
The frontend uses a simple state machine for view management:
input → loading → wrapped
↓
error
The wrapped experience is presented as a carousel with these slides:
- IntroSlide - Username and avatar intro
- ActivitySlide - Commit patterns and active days
- LanguagesSlide - Language breakdown chart
- ReposSlide - Top repositories
- CommitsSlide - Commit statistics
- GhostedSlide - Abandoned repos callout
- RoastSlide - AI-generated roast
- DevStorySlide - AI narrative
- PredictionsSlide - Next year predictions
- AdviceSlide - AI advice
- SummarySlide - Final shareable summary
Uses html-to-image library to convert slides to PNG for social sharing.
| File | Purpose |
|---|---|
index.js |
Express app setup, middleware, graceful shutdown |
config/index.js |
Environment variables, year calculation |
db/connect.js |
MongoDB connection pooling and event handlers |
models/Wrapped.js |
Mongoose schema with static methods |
routes/wrpped.js |
API route handlers with caching logic |
services/ai.js |
OpenAI integration for all AI features |
utils/github.js |
GitHub REST & GraphQL API utilities |
utils/stats.js |
Stats calculation and aggregation |
| File | Purpose |
|---|---|
App.tsx |
Main app with state management |
types/wrapped.ts |
TypeScript interfaces |
components/views/* |
View components (Input, Loading, Error, Wrapped) |
components/wrapped/* |
Individual carousel slides |
components/ui/* |
Reusable UI primitives |
| Variable | Default | Description |
|---|---|---|
NODE_ENV |
development |
Environment mode |
PORT |
3001 |
Backend server port |
MONGODB_URI |
localhost:27017/wrapped |
MongoDB connection string |
GITHUB_TOKEN |
- | GitHub PAT for API access |
OPENAI_TOKEN |
- | OpenAI API key |
CLIENT_URI |
http://localhost:5173 |
CORS allowed origin |
MIT License - see LICENSE for details.
Siyabonga Samson Lukhele
- Website: samsonlukhele.me
- Email: siyabonga.lukhele@umuzi.org
Made with ❤️ and way too much ☕