A powerful REST API built with Node.js, Express, and TypeScript that analyzes GitHub user profiles, fetches detailed statistics, and caches results in a MySQL database.
- GitHub Profile Analysis: Fetch and analyze GitHub user profiles with detailed statistics
- Data Caching: Store profiles in MySQL for optimized performance and reduced API calls
- Rate Limiting: Built-in rate limiting (200 requests per 15 minutes) to prevent abuse
- Request Validation: Input validation middleware for robust error handling
- Error Handling: Comprehensive error handling with meaningful HTTP status codes
- Logging: Structured logging for debugging and monitoring
- Docker Support: Pre-configured Docker and Docker Compose for easy deployment
- Type-Safe: Full TypeScript support for type safety and better DX
- Testing: Automated test suite with Vitest
- Runtime: Node.js 22
- Framework: Express.js 5.x
- Language: TypeScript 6.x
- Database: MySQL 8 with Drizzle ORM
- API Testing: Supertest & Vitest
- Logging: Morgan & Custom Logger
- Security: CORS, Rate Limiting, Input Validation
- Node.js 22 or higher
- npm or yarn
- Docker (optional, for containerized deployment)
- MySQL 8 (or use Docker Compose)
git clone <repository-url>
cd assignment-3npm installCreate a .env file in the root directory with the following variables:
DATABASE_URL=mysql://user:password@localhost:3306/github_analyzer
GITHUB_TOKEN=your_github_personal_access_token
GITHUB_PROFILE_CACHE_TTL=60
GITHUB_API_BASE_URL=https://api.github.com
PORT=3000Environment Variables Explanation:
DATABASE_URL: MySQL connection stringGITHUB_TOKEN: GitHub Personal Access Token for API requests (get from https://github.com/settings/tokens)GITHUB_PROFILE_CACHE_TTL: Cache time-to-live in seconds (default: 60)GITHUB_API_BASE_URL: GitHub API base URLPORT: Server port (default: 3000)
docker-compose up -dThis will start a MySQL container with the required database.
# Create database
mysql -u root -p -e "CREATE DATABASE github_analyzer;"
# Update DATABASE_URL in .env with your MySQL credentialsnpm run devServer will start with hot-reload enabled at http://localhost:3000
npm run build
npm startBuild and run the Docker image:
docker build -t github-profile-analyzer .
docker run -p 3000:3000 --env-file .env github-profile-analyzerGET /
Returns API documentation with available endpoints.
Response:
{
"name": "GitHub Profile Analyzer API",
"status": "running",
"docs": {
"analyze": "GET /api/v1/profiles/analyze/:username",
"list": "GET /api/v1/profiles/list",
"getOne": "GET /api/v1/profiles/:username",
"delete": "DELETE /api/v1/profiles/:username"
}
}GET /api/v1/profiles/analyze/:username
Fetches and analyzes a GitHub user profile, storing results in the database.
Parameters:
username(string): GitHub username to analyzeforce(query, optional): Force refresh from GitHub API (boolean)
Response:
{
"code": 200,
"message": "GitHub profile found and analyzed",
"data": {
"githubId": 1,
"username": "octocat",
"name": "The Octocat",
"avatarUrl": "https://avatars.githubusercontent.com/u/1?v=4",
"profileUrl": "https://github.com/octocat",
"bio": "There once was...",
"company": "GitHub",
"location": "San Francisco",
"blog": "https://github.blog",
"twitterUsername": "octocat",
"email": null,
"hireable": null,
"stats": {
"publicRepos": 2,
"publicGists": 8,
"followers": 3938,
"following": 9,
"followerFollowingRatio": 437.56
},
"insights": {
"totalStars": 4520,
"totalForks": 1890,
"totalWatchers": 2340,
"topLanguage": "JavaScript",
"languageBreakdown": {"JavaScript": 45, "Python": 30, "Go": 25},
"mostStarredRepo": "Hello-World",
"mostStarredRepoStars": 3000,
"accountAgeDays": 5000,
"activityScore": 8.5
},
"githubCreatedAt": "2011-01-26T19:01:12Z",
"githubUpdatedAt": "2024-01-15T12:00:00Z",
"analyzedAt": "2024-01-15T12:00:00Z",
"createdAt": "2024-01-15T12:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
}Status Codes:
200: Profile successfully analyzed and stored400: Invalid username format404: User not found on GitHub429: Rate limit exceeded500: Server error
GET /api/v1/profiles/list
Retrieves a paginated list of all cached GitHub profiles from the database.
Query Parameters:
page(number, required): Page number for pagination (default: 1)per_page(number, required): Number of profiles per page (default: 10)
Response:
{
"code": 200,
"message": "Profiles found",
"profiles": [
{
"githubId": 1,
"username": "octocat",
"name": "The Octocat",
"avatarUrl": "https://avatars.githubusercontent.com/u/1?v=4",
"profileUrl": "https://github.com/octocat",
"bio": "There once was...",
"company": "GitHub",
"location": "San Francisco",
"blog": "https://github.blog",
"twitterUsername": "octocat",
"email": null,
"hireable": null,
"stats": {
"publicRepos": 2,
"publicGists": 8,
"followers": 3938,
"following": 9,
"followerFollowingRatio": 437.56
},
"insights": {
"totalStars": 4520,
"totalForks": 1890,
"totalWatchers": 2340,
"topLanguage": "JavaScript",
"languageBreakdown": {"JavaScript": 45, "Python": 30, "Go": 25},
"mostStarredRepo": "Hello-World",
"mostStarredRepoStars": 3000,
"accountAgeDays": 5000,
"activityScore": 8.5
},
"githubCreatedAt": "2011-01-26T19:01:12Z",
"githubUpdatedAt": "2024-01-15T12:00:00Z",
"analyzedAt": "2024-01-15T12:00:00Z",
"createdAt": "2024-01-15T12:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
],
"perPage": 10,
"nextPage": 2
}Status Codes:
200: Profiles retrieved successfully400: Invalid pagination parameters429: Rate limit exceeded500: Server error
GET /api/v1/profiles/:username
Retrieves a previously cached GitHub profile from the database.
Parameters:
username(string): GitHub username
Response:
{
"code": 200,
"message": "GitHub profile found",
"data": {
"githubId": 1,
"username": "octocat",
"name": "The Octocat",
"avatarUrl": "https://avatars.githubusercontent.com/u/1?v=4",
"profileUrl": "https://github.com/octocat",
"bio": "There once was...",
"company": "GitHub",
"location": "San Francisco",
"blog": "https://github.blog",
"twitterUsername": "octocat",
"email": null,
"hireable": null,
"stats": {
"publicRepos": 2,
"publicGists": 8,
"followers": 3938,
"following": 9,
"followerFollowingRatio": 437.56
},
"insights": {
"totalStars": 4520,
"totalForks": 1890,
"totalWatchers": 2340,
"topLanguage": "JavaScript",
"languageBreakdown": {"JavaScript": 45, "Python": 30, "Go": 25},
"mostStarredRepo": "Hello-World",
"mostStarredRepoStars": 3000,
"accountAgeDays": 5000,
"activityScore": 8.5
},
"githubCreatedAt": "2011-01-26T19:01:12Z",
"githubUpdatedAt": "2024-01-15T12:00:00Z",
"analyzedAt": "2024-01-15T12:00:00Z",
"createdAt": "2024-01-15T12:00:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
}Status Codes:
200: Profile found400: Invalid username format404: Profile not found in database429: Rate limit exceeded500: Server error
DELETE /api/v1/profiles/:username
Removes a cached GitHub profile from the database.
Parameters:
username(string): GitHub username
Response:
{
"code": 204,
"message": "GitHub profile deleted successfully",
"data": {
"username": "octocat"
}
}Status Codes:
200: Profile deleted successfully400: Invalid username format404: Profile not found429: Rate limit exceeded500: Server error
Run the test suite:
npm testRun tests with coverage:
npm test -- --coverageTest files are located in src/tests/ directory.
assignment-3/
├── src/
│ ├── app.ts # Express app configuration
│ ├── server.ts # Server entry point
│ ├── config/
│ │ └── config.ts # Configuration loader
│ ├── controllers/
│ │ └── profile.controller.ts # Route handlers
│ ├── services/
│ │ └── github.service.ts # GitHub API integration
│ ├── routes/
│ │ └── profile.routes.ts # Route definitions
│ ├── middlewares/
│ │ ├── error.middleware.ts # Error handling
│ │ └── validation.middleware.ts # Input validation
│ ├── database/
│ │ ├── client.ts # Database connection
│ │ └── schema.ts # Table schemas
│ ├── utils/
│ │ ├── logger.ts # Logging utilities
│ │ └── error.ts # Error classes
│ ├── types/
│ │ └── request.ts # TypeScript types
│ └── tests/
│ └── profile.test.ts # Test suite
├── .env # Environment variables
├── docker-compose.yaml # Docker Compose configuration
├── Dockerfile # Docker image definition
├── package.json # Dependencies and scripts
├── tsconfig.json # TypeScript configuration
├── vitest.config.ts # Vitest configuration
├── tsup.config.ts # Build configuration
└── README.md # This file
The API implements rate limiting to prevent abuse:
- Limit: 200 requests per 15 minutes
- Rate Limit Header: Included in response headers
- Exceeded Response:
429 Too Many Requests
The API returns standardized error responses:
{
"error": "Error message",
"status": 400
}Common error codes:
400: Bad Request (validation errors)404: Not Found (user not found)429: Too Many Requests (rate limit exceeded)500: Internal Server Error
| Column | Type | Description |
|---|---|---|
| id | INT PRIMARY KEY | Auto-incrementing ID |
| githubId | INT UNIQUE | GitHub user ID |
| username | VARCHAR(255) UNIQUE | GitHub username |
| name | VARCHAR(255) | User's full name |
| avatarUrl | VARCHAR(255) | Avatar image URL |
| profileUrl | VARCHAR(255) | GitHub profile URL |
| bio | TEXT | User bio |
| company | VARCHAR(255) | Company name |
| location | VARCHAR(255) | Location |
| blog | VARCHAR(255) | Blog/website URL |
| twitterUsername | VARCHAR(255) | Twitter handle |
| VARCHAR(255) | Email address | |
| hireable | BOOLEAN | Whether user is hireable |
| publicRepos | INT | Number of public repositories |
| publicGists | INT | Number of public gists |
| followers | INT | Number of followers |
| following | INT | Number of following |
| followersToFollowingRatio | DECIMAL | Followers to following ratio |
| totalStars | INT | Total stars across all repos |
| totalForks | INT | Total forks across all repos |
| totalWatchers | INT | Total watchers across all repos |
| topLanguage | VARCHAR(255) | Most used programming language |
| languageBreakdown | JSON | Breakdown of languages used |
| mostStarredRepo | VARCHAR(255) | Name of most starred repository |
| mostStarredRepoStars | INT | Stars on most starred repo |
| accountAgeDays | INT | Age of GitHub account in days |
| activityScore | DECIMAL | Calculated activity score |
| githubCreatedAt | TIMESTAMP | GitHub account creation date |
| githubUpdatedAt | TIMESTAMP | GitHub profile last updated |
| analyzedAt | TIMESTAMP | When profile was analyzed |
| createdAt | TIMESTAMP | Record creation time |
| updatedAt | TIMESTAMP | Last update time |
- Create a controller method in
src/controllers/profile.controller.ts - Define the route in
src/routes/profile.routes.ts - Add validation middleware if needed in
src/middlewares/validation.middleware.ts - Write tests in
src/tests/profile.test.ts - Add documentation in this README
- Use TypeScript for type safety
- Follow Express.js conventions
- Add error handling for all async operations
- Include JSDoc comments for complex functions
- Write tests for new features
- Build the application:
npm run build - Set up environment variables in production
- Run with Docker or directly:
npm start - Monitor logs and error tracking
- Use a production-grade MySQL instance
- Enable HTTPS/TLS for API endpoints
- Rotate GitHub tokens regularly
- Monitor rate limiting and adjust as needed
- Set up proper logging and monitoring
- Check
DATABASE_URLin.env - Ensure MySQL is running
- Verify credentials and network access
- Verify
GITHUB_TOKENis valid - Check GitHub API rate limits
- Ensure user exists on GitHub
- Wait for the retry period specified in
Retry-Afterheader - Consider upgrading GitHub token permissions
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the ISC License. See the LICENSE file for details.
For support, please open an issue on the GitHub repository or contact the maintainers.
- Initial release
- GitHub profile analysis API
- MySQL caching
- Rate limiting
- Docker support