---
title: "GamJo Cloud: a family vacation app | Nathan Gamache"
description: "An invite-only web app I built for my family’s vacations: plans, shared expenses and photos in one place, installable on phones, hosted on my homelab."
url: https://www.nathangamache.com/projects/gamjo-cloud/
---

# 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.

[All projects](https://www.nathangamache.com/projects/)

- 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.](https://www.nathangamache.com/assets/img/p-gamjo-cloud.svg)

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](https://www.nathangamache.com/projects/homelab/).

Timeline

## How it came together

### The first version

The first commit, April 9, 2026

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.

### Settings, photos and fixes

April 10 to 25, April 2026

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.

### Health checks

Monitoring, May 18, 2026

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.

### Push notifications

Web Push, July 10, 2026

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.

## Related projects

### [CatalogXpert: AI Sales Assistant Platform](https://www.nathangamache.com/projects/catalogxpert/)

CatalogXpert, the venture I co-founded in 2026, is also built on FastAPI and PostgreSQL: a platform designed to host AI sales assistants for many stores at once.

Technologies: Python, FastAPI, Postgres, Chroma, Claude

[Read the case study: CatalogXpert: AI Sales Assistant Platform](https://www.nathangamache.com/projects/catalogxpert/)

### [Homelab Server Infrastructure](https://www.nathangamache.com/projects/homelab/)

The rack at home that GamJo Cloud runs on: a web server, a game server and a NAS on two UPSs, with backups every day.

Technologies: Linux, TrueNAS SCALE, RAID 1, Tailscale

[View project: Homelab Server Infrastructure](https://www.nathangamache.com/projects/homelab/)

[View all 16 projects](https://www.nathangamache.com/projects/)

## Questions about GamJo Cloud?

I am happy to go into how any part of it works. [nathanpaulgamache@gmail.com](mailto:nathanpaulgamache@gmail.com)

[Ask me about it](https://www.nathangamache.com/contact/)

---

Nathan Gamache · [nathanpaulgamache@gmail.com](mailto:nathanpaulgamache@gmail.com) · [GitHub](https://github.com/nathangamache) · [LinkedIn](https://www.linkedin.com/in/nathan-p-gamache/) · [nathangamache.com](https://www.nathangamache.com/)
