Skip to content

How It Works

Ferran Buireu edited this page Jun 13, 2026 · 3 revisions

How It Works

ContribKit never touches the GitHub API. It reads the public contributions page that GitHub renders at github.com/users/{login}/contributions, parses the cells out of the HTML, builds a calendar grid, and renders it. No token, no OAuth, no private data.


Request lifecycle (web/API)

---
config:
  look: handDrawn
  theme: neutral
---
flowchart TD
    request(["Request"])
    middleware["Middleware: rate limit + security headers"]
    validate["Validate input (Zod + value objects)"]
    usecase["Use case: fetchContributions"]
    scrape["Fetch GitHub contributions HTML"]
    parse["Parse cells (date, level, count)"]
    grid["Build 53×7 calendar grid"]
    render["Render SVG (palette, shape, background)"]
    respond["Response + cache headers"]

    request --> middleware --> validate --> usecase --> scrape --> parse --> grid --> render --> respond
Loading

Step by step

  1. Middleware applies per-IP rate limiting (100 req/min) and attaches CSP + security headers.
  2. Validation parses query/route params with Zod, then constructs domain value objects (Username, Year). If a Username exists, it is valid — invalid input becomes a typed Failure before any network call.
  3. fetchContributions(repo)({ username, year }) orchestrates the fetch through the repository interface.
  4. Fetching requests the public contributions HTML from GitHub. See Fetching Contributions.
  5. Parsing extracts each day's date, level (0–4), and exact count via regex over the HTML. See HTML Parsing.
  6. Grid building maps the parsed days onto a fixed 53×7 grid aligned to week boundaries. See Calendar Grid.
  7. Rendering turns the grid into an SVG string using the selected palette, shape, and background. See SVG Rendering.
  8. Response is returned with cache headers public, max-age=3600, stale-while-revalidate=86400 (the /api/health endpoint is the exception: no-store).

Errors never throw

Every layer returns T | Failure. A Failure is a typed discriminated union (NotFound, InvalidInput, Network, Parse). At the HTTP boundary, statusFor and messageFor map a Failure to a status code and a user-facing message — the only place that mapping lives. Nothing throws across layers.

Failure Typical cause HTTP
InvalidInput malformed username or year 400
NotFound GitHub returned 404 for the user 404
Network GitHub unreachable or non-OK status 5xx
Parse HTML structure changed, no cells found 5xx

5xx failures are logged to Better Stack with the username, failure kind, and endpoint.


See also

Clone this wiki locally