Have you ever wondered what it’s like to be a backend developer? Many think it’s just repetitive work – writing code to store and serve data. But backend development can be full of creativity and challenges, and I’ve got a story to prove it.
One of my favorite projects was building the backend for a meditation app inspired by Traditional Chinese Medicine. It was far from boring and brought plenty of exciting problem-solving moments. This app, developed by Krootl agency. Whole Body Prayer app is available for free on Google Play and the App Store.
Before diving into the technical side here’s a quick overview of the main app features:
- meditation timers with customizable background sounds
- library of videos, audios and articles on mindfulness, meditation and right posture
- daily journaling and mood monitoring
- progress tracking, achievements
On of the apps uniq features is that users results are affected by phases of the Moon and Lunar Cal
Work with Lunar Calendar
Some of the app’s features depend on seasonal changes and specific dates:
- Solstices (summer and winter),
- The beginning of seasons according to the Chinese calendar,
- Chinese New Year,
- Slight Cold.
That last one might seem particularly unfamiliar, right?
In the course of some research, I discovered that the lunar calendar divides the year into 24 unique periods, marked by events like those mentioned above, plus a few more.
Once I understood this, the task became clearer: write code to calculate these dates dynamically for any year. Finding these dates online wasn’t hard. But incorporating them into the app? Hardcoding them for a few years wasn’t an option. Automation was everything – there had to be a smarter way to make this feature work seamlessly.
Finding an API that provides these data in a ready-to-use format? Sure, but paying for access wasn’t really an option.
After a few hours of searching, I came across some resources that pointed me in the right direction.
One article that particularly caught my attention was titled Solar Terms. One of its paragraphs stated:
“In modern astronomical parlance, the 24 solar terms are defined as the times when the apparent geocentric ecliptic longitude of the Sun, λs, reaches integral multiples of 15°.”
And what a stroke of luck — just a little earlier, I’d stumbled upon a fascinating Python library called PyMeeus, which does exactly what I needed: calculate the geocentric coordinates of the Sun for a given date (and much more).
It’s a bit funny in the context of using this data to occasionally change the app’s background or provide users with a small bonus, isn’t it? My colleagues certainly teased me about it.
Finally, the puzzle came together.
All that remained was to create a simple Enum with the necessary events and their corresponding longitudes and write a small function to calculate the longitude for a given date.
def get_solar_degrees(date: datetime.date) -> int:
date_time = datetime.datetime.combine(date, time(12, 0, 0))
epoch = Epoch(date_time)
longitude, b, r = Sun.geometric_geocentric_position(epoch)
return int(longitude)
Did you know how to determine the date of the Chinese New Year? It’s quite simple: take the second new moon after the winter solstice. Or... maybe it’s not that simple after all? Thankfully, I now had a tool to do this. PyMeeus can also calculate lunar phases, which allowed me to find the date of the Chinese New Year with remarkable accuracy (I cross-checked it against future dates provided by Google).
Here’s an example of how I used PyMeeus to calculate this special date:
def find_new_year_date(year: int) -> datetime.datetime:
asked_year = year
year -= 1
equinox = Sun.get_equinox_solstice(year, target="winter")
lower_limit = datetime(asked_year, 1, 21)
phase = Moon.moon_phase(equinox, target="new")
new_moon_date = datetime(*phase.get_full_date()[:5])
while new_moon_date < lower_limit or new_moon_date.date() == lower_limit.date():
equinox = phase + 29
phase = Moon.moon_phase(equinox, target="new")
new_moon_date = datetime(*phase.get_full_date()[:5])
return new_moon_date
Perhaps this will convince some people that development is not just about boring code; it can be much more interesting than they thought.
When NoSQL Becomes Too NoSQL
For the development of this project, I decided to use MongoDB—a powerful and flexible database. Its ability to work with unstructured data and lack of a strict schema seemed like the perfect tool (especially since I had only used SQL solutions before). However, I didn’t immediately grasp where the boundaries of its application lay.
Earlier, I mentioned that users have a “score” that is dynamically calculated based on various parameters and mathematical operations. Additionally, the calculation principle changed multiple times during development, which made it challenging to store it in a finalized form.
I admit I got carried away with how flexibly MongoDB can aggregate data from documents. In an attempt to distribute the workload between the server and the database, I ended up shifting the responsibility for most mathematical operations onto Mongo. (I know, it’s a silly mistake, but now I’m here to save you from making a similar one.)
Testing this in a controlled environment worked perfectly. However, it’s important to consider that in real-world conditions, MongoDB instances are not only fewer in number compared to server instances but also tend to be less powerful. Consequently, I soon faced the need to address my mistakes by optimizing queries and reducing their volume.
I want to share what helped me during the optimization process. First, how could I locally create conditions similar to reality? For me, the answer was stress testing—simulating a large number of users with varying behaviors to track which specific endpoints were causing database overload. Locust became a valuable tool for me here. It partly replaced the workflow I had set up in Postman, as it allowed me to conveniently control which data would be sent and where to store the responses to requests. And of course, it provided statistics during the testing.
Secondly, how could I identify which queries were problematic and why? Naturally, MongoDB has its own EXPLAIN feature, similar to SQL. However, I don’t always have the patience to fiddle with the console and go through all the queries affected by various endpoints. So, I turned to the UI solution built into MongoDB Compass:
Initially, you can always check in real-time what’s happening with your document collections by navigating to the database information tab.
And navigate to the Performance tab.
In this section, you will be able to see which collections are currently in use and which queries are taking the longest to execute.
Once you identify a problematic query, you can copy it into the Aggregation tab of the corresponding collection to find out what exactly is causing the delays by using the Explain feature.
Python ODM for MongoDB - Beanie
We all know that relational databases are not fond of JOINs, and non-relational databases dislike them even more.
Perhaps my next small note will help someone in their project. For interacting with MongoDB, I used the Beanie library. I recommend paying attention to the parameter that controls the nesting depth of the entities you want to retrieve from your collections (nesting_depth). This helps avoid unintentionally forcing MongoDB to perform numerous unnecessary $lookup operations in your queries.
I hope you enjoyed reading about the journey behind building this meditation app and found it insightful. If you're planning to develop meditation app or have an idea you’d like to bring to life, feel free to reach out to our team at Krootl. Let’s create something amazing together!
Farewell 👋