Home

Engineering behind Trakkr

There is a reason why this page doesn't have a light mode.

Warming up: What to expect..?

I get it, this seems interesting. So let me tell you what we are going through here. First I will tell you what problem this application aims to solve, then very quickly we move on to the high level design of this application.

Still with me? Okay, then we will go through the database design of this project. After that I will quickly share how I built some of the most interesting features of this project. I will conclude by mentioning the tech stack.

Let's begin!

Once upon a time...

Chintu, a dedicated gym-goer, wanted to improve his lifts. He knew progressive overload was key, so he started logging workouts—using Google Notes or, worse, his memory. Every Monday, he either forgot last week’s numbers or struggled to compare progress (because Google Notes wasn’t built for tracking gains).
No structure. No standardization. Just confusion.

That’s where Trakkr comes in. No more scattered notes or forgotten lifts—just a clean, structured way to log, compare, and improve.


High level diagram


Database Design


There are APIs Actions!

Remix (framework of this web application) provides server actions for data mutations (backend). So, this way every route which has an 'action' also becomes an API route. Sounds cool, here is an example:

Remix takes you back to the traditional way of dealing with mutations.
Similarly, for 'GET' requests, there is a loader function like action which executes before the page gets loaded.


Let's talk about features

Let me discuss with you the approach with which I implemented 5 main features of this application in descending order of interesting-ness.

01 AI Analysis

This is implemented through an AI agent (RAG) built using Langgraph. The agent extracts the last 10 workouts from the database, submits them to LLM, then saves the structured output from the LLM to the database. The flow is shown below:

02 Workout Logging

The user logs his reps and weight lifted in exercises for every set he does. But Utkarsh this is simple CRUD, why is it here?!
I want the user to get almost realtime updates, even if server and database and quite busy. So, I used Redis (in memory database) to achieve this. This way, the load on the primary database is reduced, ensuring real-time updates and an interruption-free workout. After the user finishes the workout, the application saves all the data to the database in one bulk operation.

03 Image Upload

This is an interesting one! I was keen on building a scalable system this time, so I decided to keep a separate service dedicated to uploading images, so that this heavy lifting job is not done by my cute core-service. The following flow is not perfect at all, I should have vertically partitioned the database and given the image upload service a dedicated database, but it is good to go I guess.
This is how it goes, have a close look:

04 Sending notifications

Again, my aim was to implement a scalable notification sending system. So, I decided to make another dedicated service for this. Currently image upload service and notification sending system are unified as feature-service but they can be separated pretty easily when the need arises. The notifications go to the feature-service through a queue and that helps to absorb data spikes.
This feature could have been much better by using 2 queues, one for urgent notifications and one for low priority ones but I again think that this is good to go.

05 Dark/Light mode

This is an easy one, I just used CSS variables and changed their values depending on whether there is a 'dark' class in the html tag. Then I simply used basic DOM manipulation to toggle this class in html tag and saved the theme in local storage.

Bonus Fuzzy search

While creating the workout even if you type 'Tarbell Tench Press', the first result would still be 'Barbell Bench Press'. I did not use any machine learning algorithm to do this. Just a tiny bit of research and I found that PostgreSQL has an extension pg-trgm which solves exactly this problem. It generates triagrams of the two strings and then computes a similarity score of between 0 and 1. I simply returned 10 results whose similarity score is greater than 0.3 in descending order.


Tech Stack

Built completely in Typescript.

Remix as the full stack framework. Why? Amazing developer experience and performance.

Tailwind CSS and Shadcn UI for styling. Why? Easy and quick to use.

Prisma as ORM. Why? To talk to SQL like NoSQL.

PostgreSQL as primary database. Why? SQL because I don't like the schema flexibility which a NoSQL database gives. Moreover, it gives the feature of fuzzy searching out of the box.

Redis as a server-side cache. Why? To give users seamless experience while logging the workouts.

RabbitMQ as the message queue. Why? To communicate between the services.

Langchain/Langgraph . Why? To build the RAG agent.

OpenAI as the LLM. Why? To do the AI analysis on the retrieved data.

Docker to containerize the application.

Nginx as a reverse proxy.

Deployed on an AWS EC2 machine.


Designed and built with ❤️ by

Utkarsh Ojha

Gmail logoLinkedIn logoX logo