Dots over squares - GitHub mosaic
27 APR. 2026, LUCA BULLETTI
RAILS GITHUB GRAPHQL
A quick guide on how to add your personal GitHub activity mosaic to your rails app. This covers GraphQL API calls, caching and some simple css flexboxing.
To add some flair to my landing page, I decided to visualize my coding activity just like the mosaic on my GitHub profile would. After some quick online search, I found the required GitHub GraphQL API call on a medium article and decided to go from there.
Requirements
Before hacking away, I needed to create a new github API token. It's hidden under Settings > Developer Settings. Make sure to set a reminder if you choose to add an expiry date, which is heavily recommended. The public repo scope is all that is needed to get the same data that is displayed on the github mosaic.
GitHub API client
I added a new directory app/services/github_api, where I created two files: client.rb and mosaic.rb. To keep dependecies light, I opted for the already included NET::HTTP to send the API call.
# app/services/github_api/client.rb
require "net/http"
module GithubApi
class Client
ENDPOINT = URI("https://api.github.com/graphql")
def query(gql: {}, variables: {})
response = Net::HTTP.start(ENDPOINT.host, ENDPOINT.port, use_ssl: true) do |http|
request = Net::HTTP::Post.new(ENDPOINT)
request["Authorization"] = "Bearer #{api_token}"
request["Content-Type"] = "application/json"
request.body = { query: gql, variables: variables }.to_json
http.request(request)
end
JSON.parse(response.body).dig("data")
end
private
def api_token
Rails.application.credentials.github_api_token
end
end
end
As you can see, the API token is stored in my rails credentials. To edit those, simply enter rails credentials:edit in your console. If I ever need more data from GitHub than my activity, this client class will allow me to make any GraphQL query I might come up with in the future.
Query-wrapping class
# app/services/github_api/mosaic.rb
module GithubApi
class Mosaic
MOSAIC_QUERY = <<~GQL
query($username:String!) {
user(login: $username) {
contributionsCollection {
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}
GQL
def initialize
@result = Rails.cache.fetch("github_mosaic", expires_in: 12.hours) do
Client.new.query(gql: Mosaic::MOSAIC_QUERY, variables: { username: Rails.application.credentials.github_username })
end
@contributions = @result["user"]["contributionsCollection"]["contributionCalendar"]
end
def total
@contributions["totalContributions"]
end
def weeks
@contributions["weeks"]
end
end
end
To prevent calling GitHub's API on every page load, I used the low level caching technique described on the official rails guides. This ensures that the data is only fetched once a day. Now that we have a way to fetch activity data from GitHub, let's serve it to a view through a controller.
# app/controllers/home_controller.rb
class HomeController < ApplicationController
# ...
def index
# ...
@mosaic = GithubApi::Mosaic.new
@weekly_contributions = @mosaic.weeks
@total_contributions = @mosaic.total
end
end
Visualisation
All that is left to do is to add some component (in this case a partial view) to display the data.
<!-- app/views/home/_mosaic.html.erb -->
<div class="flex gap-0.5 sm:gap-1">
<% weeks.each do |week| %>
<div class="flex flex-col gap-0.5 sm:gap-1">
<!-- week is a hash with a single key-value pair -->
<% week.values.flatten.each do |day| %>
<span
title="<%= pluralize(day["contributionCount"], "contribution") %> on <%= DateTime.parse(day["date"]).strftime('%b. %d') %>"
class="<%= contribution_styles(day["contributionCount"]) %>">
</span>
<% end %>
</div>
<% end %>
</div>
To make each week appear vertically like on GitHub, only two tailwind-css classes are needed: flex flex-col. The weeks themselves are further flex-ed "normally" in a row. Using the same gap values allows for consistent spacing around the days. In order to display the amount of contributions and date of a day, I added a title attribute to each of them. To style days visually, I added a helper method in home_helper.rb:
# app/helpers/home_helper.rb
module HomeHelper
def contribution_styles(daily_contributions)
color = case daily_contributions
when 0
"bg-accent-base/10"
when 1..3
"bg-accent-base/50"
when 4...7
"bg-accent-base/75"
else
"bg-accent-base"
end
"#{color} rounded-full size-1 sm:size-2"
end
end
The amount of contributions per day determines the background colour of the day in the matrix. Note that it is preferable to register different shades of your colours as tailwind variables instead of using the opacity modifier (.../10, .../50 etc.) like I did here. With the size and rounded-full classes, the days appear as dots.
To marvel at the result, you can check out my landing page.
TLDR;
- Create / configure an API client and the according query under
app/services/* - Fetch and cache the required data in your controller
- Wrap the data in some flexboxes and implement colouring logic