Skip to content

GamJo Cloud

Every summer my family rents a house up north in Michigan for a big vacation together, and we needed a better way to organize it and keep it all in one place: the schedule, the receipts, the photos. So I built GamJo Cloud, just for us. It is a progressive web app, so we can add it to our phones’ home screens and open it like any other app. We used it on the 2026 trip and will use it again in 2027. Only people I invite can sign in.

  • Role Sole developer
  • Built for My family
  • Started April 2026
  • In use Summer 2026 · Again in 2027
  • Runs as An app on our phones
  • Hosted on My homelab
  • Access Invite only
  • Stack Python · FastAPI · Postgres · React · PWA
A drawing of how GamJo Cloud fits together: the family’s phones run the installed app, built with React and Vite, which talks to a FastAPI back end that handles the itinerary and votes, expenses and balances, the photo gallery, the activity log, and invites and sign-in, with its data in PostgreSQL. A separate health check service checks the database for the uptime monitors. The back end sends Web Push notifications to the app, sends sign-in codes by email through Resend, and makes smaller copies of large photos with Pillow.

A drawing of how the pieces fit together, not a screenshot.

What it is

I built GamJo Cloud as one hub for the whole trip, in place of scattered group texts and shared documents. It has a shared itinerary the family votes on, expenses split between family groups, a photo gallery, and notifications on our phones.

The home screen shows what is left on today’s plan, a countdown before the trip and the day count during it, the weather where we are staying, the latest photos, and a feed of recent activity. That feed comes from an activity log that records new and deleted expenses and new plans, and keeps a copy of every expense that gets deleted.

Nobody signs up. I invite each person by email from the admin screen, and after that they sign in with a code sent to their inbox. An admin can also copy a trip, with its people and family groups and its dates moved a year on, to set up the next one.

I am its only developer. It is still live and runs on my homelab.

Timeline

How it came together

  • April 9, 2026

    The first version

    The first commit

    I started GamJo Cloud that week, and the first commit already had the core in place: the itinerary with voting, expenses split between family groups, the photo gallery, the activity log, sign-in by email code, invites, the admin screens, and the installable app with its service worker.

  • April 2026

    Settings, photos and fixes

    April 10 to 25

    The app began picking its greetings, confirmations and other messages at random from lists of options, and it got new icons. Theme, reduced motion and the accessibility mode started saving to each person’s account, a round of accessibility fixes followed, and tapping someone’s picture on the home screen started opening it larger. On April 25 the server began making smaller copies of large photos and turning those copies the right way up, the photos and gallery pages became one, and the theme got a setting that follows the phone.

  • May 18, 2026

    Health checks

    Monitoring

    I added a health service that runs as its own process, apart from the API. It has a check that the process is up and one that also queries the database and answers 503 when it cannot reach it. Two Better Stack uptime monitors watch the app, one on the front end and one on the back end.

  • July 10, 2026

    Push notifications

    Web Push

    The installed app can now get notifications. The profile screen has a switch to turn them on and a button that sends a test, and when someone adds a photo, everyone else who has them turned on hears about it.

Capabilities

What it does

01 · Itinerary

Plans the family votes on

Anyone on the trip can add a plan with a date and a time. Plans are grouped by day and sorted by time, and each day can have its own title. Everyone can like or dislike a plan, and the names of who voted each way show under it. An admin locks a plan once it is confirmed, and can put one plan at a time up for a vote in a spotlight on everyone’s home screen.

02 · Expenses

Split between family groups

Anyone can add an expense, say who paid, attach a photo of the receipt, and pick which family groups share it. Each group has a percentage. The app works out what each group owes against what its members paid, then lists who pays whom to settle up. An admin marks a group settled once it has paid, and can export every expense to a CSV file.

03 · Photos

One shared gallery

People upload many photos at once and see how many are done. When a photo is larger than the grid or the full-screen viewer needs, the server uses Pillow to make a smaller copy for it, at most 400px on its longest side for the grid and 1200px for the viewer. The copies are turned the right way up from the EXIF data, and the original is kept. The gallery is grouped by date, and people can edit their own photos and delete them one at a time or several at once.

04 · Sign-in

A code by email, no passwords

You enter your email and get a code that works only once. Typing a code keeps sign-in inside the installed app, where a link in an email would open the phone’s browser instead. The screen remembers where you were while you check your email, and pasting the code signs you in.

05 · Notifications

Web Push on our phones

When someone adds a photo, everyone else on the trip who has turned notifications on gets one, on Android and on iPhones with the app on the home screen. A batch of photos shows as one notification, and tapping it opens the gallery. On an iPhone that has not installed the app yet, the profile screen explains how to add it to the home screen first.

06 · Settings

Theme and accessibility

Each person picks a light, dark or system theme, can turn off animations, and can turn on an accessibility mode with 20px text, black or white text and heavier borders. The choices are saved to their account, so they carry over to another device. The app also follows the phone’s own reduced-motion setting, and small controls are at least 44px on touch screens.

Notifications

How notifications are sent

Each notification goes out through the push service of the phone’s browser. The code around that:

  • Notifications go out in the background after the upload has already been answered. Each send runs in a worker thread with a 10-second limit, so a slow push service cannot hold up the server.
  • The server only accepts subscriptions whose address is on the domain of one of the major browsers’ push services.
  • When a push service says a subscription is gone, the server deletes it. A message waits up to a day for a phone that is offline.
  • The person who added the photo is left out of the notification.
  • Subscribing the same browser twice updates one record instead of making two, even when two tabs do it at the same moment.
  • If the browser replaces a subscription on its own, the service worker subscribes again and sends the new one to the server. When someone turns notifications on, the app checks that their subscription matches the server’s current key and replaces it if not.
  • On a phone more than one person signs in to, the subscription moves to whoever is signed in now.

Architecture

How it is built

  • The back end is Python on FastAPI, with async SQLAlchemy and asyncpg on PostgreSQL. Its routes, under /api, cover sign-in, trips, the itinerary, expenses, photos, the admin screens, the activity log and push.
  • Sessions are kept in the database and in an HTTP-only cookie, and signing out deletes them.
  • The front end is React 18, built with Vite, with one hand-written stylesheet and no CSS framework. Dark mode swaps the stylesheet’s color variables. The accessibility mode swaps the text colors to black or white and adds its own rules for larger text and heavier borders.
  • The service worker loads pages from the network first and falls back to its cache when there is no connection. Uploaded files come from the cache once they have been loaded, since they never change. It also shows the notifications.
  • Email goes through Resend: sign-in codes, and a welcome email for each new person.
  • Web Push uses pywebpush with VAPID keys, and the repository has a script that makes the key pair.
  • The weather on the home screen comes from Open-Meteo’s free geocoding and forecast APIs, which need no key.
  • Health checks run in their own process: one says the process is up, and the other also checks the database.

Questions about GamJo Cloud?

I am happy to go into how any part of it works. nathanpaulgamache@gmail.com

Ask me about it