A few weeks ago I had no idea what Gunicorn was, had never touched PostgreSQL, and had never written a single line of Django. This week I connected a live fitness API to a web app I built from scratch and generated a downloadable shareable card with real data on it. That feels pretty wild to say out loud.
Let me back up and explain what I have been building, because I think the concept is actually kind of interesting.
🏋️ The Idea: A Fitness Identity Card
While researching project ideas, I kept coming back to apps like Spotify Wrapped, that end of year card everyone shares on social media. I wanted to make something like that but for fitness. Something where you connect your Strava or Fitbit account, pick which stats matter to you (total distance, longest run, active days, elevation gained, etc.), arrange them however you want, and then share a link or download a card image. I am calling it FitCard.
The gap I noticed is that nothing like this really exists in a clean, social friendly way. There are stat trackers out there, but none of them give you a fully personalized shareable card you could post on Instagram or hand to a recruiter as proof you actually follow through on things.
🔑 The Part That Broke My Brain: OAuth
Getting Strava connected was honestly the hardest part of the whole week. Not because the code was complicated, but because I did not fully understand what OAuth actually was before I started.
Here is the short version of what I learned: OAuth is a system where instead of giving your password to a third party app, you get redirected to the original service (Strava, in this case), you approve access there, and then Strava hands back a token to my app. That token is what I use to make API calls on your behalf. No passwords stored, no security nightmare.
The first time the redirect worked and I saw "Strava Connected" show up on my dashboard, I genuinely did not believe it worked on the first try.
Once I had the token I could call Strava's API and pull back real stats. Here is basically what that looks like:
headers = {'Authorization': f'Bearer {access_token}'}
stats = requests.get(
f'https://www.strava.com/api/v3/athletes/{athlete_id}/stats',
headers=headers
).json()
The fact that a few lines of Python can reach out to Strava's servers, grab someone's running history, and display it on a page I built still kind of amazes me.
🎛️ Making It Feel Like a Real Product: Drag and Drop
One of the features I really wanted was the ability to customize which widgets show on your card and arrange them however you want, kind of like how you can move apps around on an iPhone home screen. I used a JavaScript library called SortableJS for this, which handles all the drag logic. I just had to wire it up to save the new positions back to the database whenever you drop a widget in a new spot.
Sortable.create(grid, {
animation: 150,
ghostClass: 'sortable-ghost',
onEnd: function() {
// save new positions to the database
}
});
What I did not expect was how satisfying that would feel to use. Dragging a widget, watching it snap into place, refreshing the page and seeing it stay. That is when it started feeling like a real app rather than a class project.
🖼️ The Downloadable Card
The last piece this week was generating an actual downloadable image of your FitCard using a Python library called Pillow. This was different from everything else I had done because instead of building a web page, I was drawing pixels. Laying out text, colors, and shapes programmatically onto a canvas and then saving it as a PNG file.
img = Image.new('RGB', (800, 500), (26, 26, 26))
draw = ImageDraw.Draw(img)
draw.text((40, 40), "FitCard", fill=(0, 229, 160), font=font_large)
My first version had a thick black border around the card that looked terrible. Fixing it was a two line change, but it is the kind of thing you only notice when you actually look at the output. That is a lesson I keep relearning: you have to test your own work with fresh eyes.
🚀 What's Coming Next
The app works but there is still a lot left to do. The VM's IP address keeps changing every time I restart it, which is annoying and something I need to fix with a static IP. I also want to add support for a second fitness platform and make sure the public profile page shows real data instead of placeholder values.
But the core loop works: connect an account, pick your widgets, arrange them, view your public card, download the image. For three weeks of work starting from zero Django knowledge, I am pretty happy with where this is.
Thanks for reading. More updates coming as the project continues.
Top comments (1)
This is an amazing project, I love what you are building. Would love to try it out when it's live