<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sindhu15</title>
    <description>The latest articles on DEV Community by Sindhu15 (@sindhu15).</description>
    <link>https://dev.to/sindhu15</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F648157%2F2a902cf4-a299-4118-807c-b4394d884dc5.jpg</url>
      <title>DEV Community: Sindhu15</title>
      <link>https://dev.to/sindhu15</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sindhu15"/>
    <language>en</language>
    <item>
      <title>Uplift - for Strava athletes</title>
      <dc:creator>Sindhu15</dc:creator>
      <pubDate>Wed, 18 Jun 2025 14:44:38 +0000</pubDate>
      <link>https://dev.to/sindhu15/uplift-for-strava-athletes-26hg</link>
      <guid>https://dev.to/sindhu15/uplift-for-strava-athletes-26hg</guid>
      <description>&lt;p&gt;I’m a developer who’s into fitness and now slowly falling in love with running.&lt;/p&gt;

&lt;p&gt;A few months ago, I joined a small running group guided by a coach. Initially, I couldn’t understand why people enjoyed running so much. I struggled, gasped for air, and mostly kept going because I needed a distraction.&lt;/p&gt;

&lt;p&gt;When I hit my first non-stop 10k I couldn't stop comparing it with my very first run and appreciate how far I had come. That moment stuck with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Why
&lt;/h2&gt;

&lt;p&gt;A race was coming up for our group. I couldn’t participate due to an injury, but I knew many were giving it their all — their first 5K or 10K. I wanted to build something to celebrate their progress and efforts. And for myself, it was a chance to practice system design by building something end-to-end.&lt;/p&gt;

&lt;p&gt;And so, Uplift was born — a quick weekend project built to celebrate each runner’s journey using their Strava data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Goals
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Appreciate the effort of runners in my group&lt;/li&gt;
&lt;li&gt;Show them how far they've come using Strava stats&lt;/li&gt;
&lt;li&gt;Generate a motivating, AI-powered message based on their data&lt;/li&gt;
&lt;li&gt;Ship fast and keep it simple&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;: Node.js with Koa (my comfort zone)&lt;br&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: HTML, CSS, Vanilla JS (needed speed)&lt;br&gt;
&lt;strong&gt;Integrations&lt;/strong&gt;:&lt;br&gt;
Strava API (OAuth + Activity fetch)&lt;br&gt;
OpenAI (with fallback to Together AI)&lt;br&gt;
QuickChart for journey graphs&lt;br&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Render (simple and quick)&lt;br&gt;
&lt;strong&gt;Logging&lt;/strong&gt;: Google Sheets + Script&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Breakdown
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Strava OAuth&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registered the app to get &lt;strong&gt;&lt;em&gt;client_id&lt;/em&gt; and &lt;em&gt;client_secret&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Used &lt;strong&gt;&lt;em&gt;OAuth flow&lt;/em&gt;&lt;/strong&gt; to get access and refresh tokens&lt;/li&gt;
&lt;li&gt;Set up a callback route &lt;strong&gt;&lt;em&gt;/callback&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Read user activities using &lt;strong&gt;/athlete/activities&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Followed their guidelines on logo placements etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9s4yszk1za2owmghfg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft9s4yszk1za2owmghfg2.png" width="692" height="1310"&gt;&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febaz5cg8uikzvscs9tzk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febaz5cg8uikzvscs9tzk.png" width="696" height="1340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report Generation&lt;/strong&gt;&lt;br&gt;
Each report includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Total runs and distance&lt;/li&gt;
&lt;li&gt;Longest run&lt;/li&gt;
&lt;li&gt;Fastest run&lt;/li&gt;
&lt;li&gt;Recent Run&lt;/li&gt;
&lt;li&gt;First run (to see the contrast)&lt;/li&gt;
&lt;li&gt;A chart showing their weekly run stats&lt;/li&gt;
&lt;li&gt;A motivational AI insight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;** AI Insight**&lt;br&gt;
I crafted a detailed prompt asking the AI model to write a personalized, friendly uplifting message celebrating the user’s journey with the data provided to the LLM.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI GPT-3.5&lt;/strong&gt; Turbo as the default model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Together.ai Mistral-7B-Instruct&lt;/strong&gt; as fallback&lt;/li&gt;
&lt;li&gt;Plain text as fallback if both models fail (If we exceed the quota in both models) &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Graphs&lt;/strong&gt;&lt;br&gt;
Used QuickChart to generate a visual summary of the user’s consistency&lt;br&gt;
Simple, fast and suited for small-scale usage&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download the report as an image&lt;/strong&gt;&lt;br&gt;
Initially added a text asking the user to manually take a screenshot. After I was done with the design tweaks and data I wanted to show, I realised manual screenshot was not very efficient as the user had to scroll down and take 2 images if the report was slightly longer. So now had to support downloading as an image as that was more user friendly. Used &lt;strong&gt;html2Canvas&lt;/strong&gt; to support this usecase.&lt;/p&gt;

&lt;p&gt;Strava-hosted profile images didn’t render in the canvas export. I created a proxy route to handle image downloads properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taking it a step further&lt;/strong&gt;&lt;br&gt;
I tested on my Strava account. With the race just a day away, I wanted to surprise the group. I enabled international transactions on my card and paid around $5 to enable better AI responses (hello, crazy mode 😄), and told the group something exciting was coming up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Roadblock: Connected Athletes Limit
&lt;/h2&gt;

&lt;p&gt;Everything was ready and I asked a friend to try it out, thats when I realized there was a limit on the number of users allowed to connect.&lt;/p&gt;

&lt;p&gt;I panicked. I had already told my group there was a surprise waiting. I really wanted people to use it right after the race. I wrote an email to Strava with the screenshots of my app, my linkedIn profile etc. I activated my linkedIn premium and reached out to support people from Strava requesting if they could do anything to increase it.  &lt;/p&gt;

&lt;p&gt;Eventually, I calmed down, figured out the right process, and submitted the form. I was transparent and informed my group about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polish &amp;amp; Improvements
&lt;/h2&gt;

&lt;p&gt;With extra time while waiting for the increased limit, started fine tuning a few things.&lt;/p&gt;

&lt;p&gt;Refined prompt&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging&lt;/strong&gt;&lt;br&gt;
I wanted to understand the basic usage of my app. But I also wanted something very quick and didn't want to spend too much time on this. &lt;strong&gt;Google sheets&lt;/strong&gt; with a script deployment was sufficient for this.&lt;br&gt;
Used Google Sheets + Script deployment to log basic usage info: &lt;strong&gt;timestamp, model used, and origin&lt;/strong&gt;. No user data was stored — just a &lt;strong&gt;hashed athlete ID&lt;/strong&gt;. Privacy first!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz9r7veaybefbbzut3oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz9r7veaybefbbzut3oq.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supported &lt;strong&gt;error states&lt;/strong&gt; with friendly messages
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftp0c12viollk8ws8drd1.png" alt="Image description" width="688" height="1130"&gt;
&lt;/li&gt;
&lt;li&gt;Created *&lt;em&gt;feedback form *&lt;/em&gt;(Google Form)&lt;/li&gt;
&lt;li&gt;Added email contact for support&lt;/li&gt;
&lt;li&gt;Replaced the “latest run” with more meaningful milestones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Caching&lt;/strong&gt;&lt;br&gt;
Added caching to handle Strava's 200-requests-per-15-min rate limit&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design Iterations&lt;/strong&gt;:&lt;br&gt;
I made &lt;em&gt;n&lt;/em&gt; number of tweaks on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Text placement&lt;/li&gt;
&lt;li&gt;Font sizing&lt;/li&gt;
&lt;li&gt;Chart layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Logo&lt;/strong&gt;:&lt;br&gt;
Spent an unreasonable amount of time creating the logo and the name 😅&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u9qeiwdwtpcr682g680.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1u9qeiwdwtpcr682g680.png" alt="Image description" width="124" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a privacy first app. No user data is stored. &lt;/p&gt;

&lt;h2&gt;
  
  
  End Result
&lt;/h2&gt;

&lt;p&gt;Here goes my report :D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyfewhevq26445yni6yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmyfewhevq26445yni6yn.png" alt="Image description" width="660" height="2354"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My takeaways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OAuth&lt;/li&gt;
&lt;li&gt;Got to explore Strava APIs&lt;/li&gt;
&lt;li&gt;AI prompting and data feeding for AI insights&lt;/li&gt;
&lt;li&gt;Starting with an idea, building and launching a product end-to-end&lt;/li&gt;
&lt;li&gt;Feedback from real users &lt;/li&gt;
&lt;li&gt;Always check rate limits and access caps&lt;/li&gt;
&lt;li&gt;Ship fast, but communicate clearly when things go sideways&lt;/li&gt;
&lt;li&gt;Tiny touches — profile pics, download buttons, fun copy — make a big difference&lt;/li&gt;
&lt;li&gt;Excitement when users used my app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try Uplift!
&lt;/h2&gt;

&lt;p&gt;If you’re a runner on Strava, try it out and see your journey celebrated beautifully. &lt;br&gt;
&lt;a href="https://sindhus-stride.onrender.com" rel="noopener noreferrer"&gt;https://sindhus-stride.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-requisite:&lt;/strong&gt; A Strava account with a few runs logged. &lt;/p&gt;

&lt;p&gt;Uplift is simple. But it’s made with heart and all the tech I have mentioned in this blog :P A weekend well spent :)&lt;/p&gt;

&lt;p&gt;Here's me: &lt;a href="https://www.linkedin.com/in/sindhu-a15/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;br&gt;
Also published on medium - &lt;a href="https://a-sindu15.medium.com/uplift-for-strava-athletes-1317ad5b9645" rel="noopener noreferrer"&gt;https://a-sindu15.medium.com/uplift-for-strava-athletes-1317ad5b9645&lt;/a&gt;&lt;/p&gt;

</description>
      <category>strava</category>
      <category>ai</category>
      <category>javascript</category>
      <category>oauth</category>
    </item>
  </channel>
</rss>
