Adding server-side functions to my tiny responsive web apps

A month or so ago, I wrote a story explaining the stack and tool-chain I’ve put together for building tiny responsive web apps.

The technology I chose solved these problems:

  • I only want to pay for what I use (scale-to-zero)
  • I don’t have a lot of time available for learning or building (no steep learning curves without substantial time-savings)
  • I don’t have time for maintenance activities (no patching servers, automated scale-up)
  • I’m not a good UI designer or front end engineer (design systems are great)

My stack works really well for an app with standard create/retrieve/update/delete operations, but I recently hit a requirement needing more sophistication on the back end.

The business problem

  • A timed, unguided meditation
  • Ability to continue when the timer has finished if wanted
  • Ability to pause and resume a session
  • A calming, ambient, background sound loop
  • A periodic bell to check I’m still in the moment
  • Random intermittent, but relevant, sounds
  • An automatic diary of sessions

The new requirement

My current tool-kit looks like this:

It’s a two-tier architecture, so all the business logic lives client-side in the browser. This is fine for the current requirements, but things change when I need to compute statistics. I could stick with this stack and count the streaks from the sessions in my diary client-side, but I’m getting close to 200 sessions in my diary now. If I play that forward a couple of years, then to render my stats page, I’ll be retrieving a lot of data from Firebase Realtime Database every time it loads and unnecessarily repeating the calculation in the browser. If I one day have a whole lot of users, then there’s going to be a lot of data unnecessarily being retrieved a lot of the time, and that costs money.

Firebase triggers

I followed the example using the onUpdate() trigger detailed in the docs. I wrote a function to figure out the longest streak from a list of diary entries and hooking it up was just a matter of grabbing the entries from the after field on the snapshot provided on update and writing the result back to the parent node. Every time a user adds or removes a meditation session, the stats for that user is automatically recalculated. With the stats just sitting in the database, my app can read the result via the usual Firebase API calls.

exports.countStreaks = functions.database.ref('diary/{userUid}/entries')
.onUpdate(async (snapshot, context) => {
const afterEntries = Object.entries(snapshot.after.val());
const processedEntries = [];

for (const [key, value] of afterEntries) {
const finishTime = DateTime.fromMillis(value.finishTime);
const diaryEntry = {finishTime, totalSeconds: value.totalSeconds};
processedEntries.push(diaryEntry);
}
const stats = statscalculator.longestStreak(userTimezone, processedEntries);
if(stats){
return snapshot.after.ref.parent.child('stats').set(stats);
}
});

Summary

Boom.

Technology leader for Xero in Auckland, New Zealand, former start-up founder, father of two, maker of t-shirts and small software products