Fullstack

Fullstack

5 pieces of work

Tagly

Three apps, two of them live on both stores

Built 3 React Native apps from scratch. The customer and business apps are live on the App Store and Google Play, and the internal admin dashboard is distributed through TestFlight. I handled the code, payments, deployment and infrastructure, as well as the App Store and Google Play reviews and listings. Tagly: Share and Earn peaked at number 1 in the Lifestyle category on the Czech App Store on 15 August 2026.

  • EAS Build and Submit
  • App Store Connect
  • Google Play Console
  • Instagram Graph API

Tagly

Why Tagly couldn't ship as an MVP

For Tagly to work, we first had to pass Meta App Review, and passing it requires a full product, so Tagly couldn't ship as an MVP. Tagly also moved real money in both directions, which left no room for error. There's still a lot I decided against building:

  • Job queues live in Postgres instead of a separate queue service, so there's nothing extra to run or pay for in each environment.
  • The REST API and all 7 background services ship as one Docker image, told apart by a single environment variable.
  • An admin approves every share by hand before it's paid, rather than leaving it to automation alone.
  • The admin app stayed internal: iOS only, English only, installed through TestFlight. It updates together with the backend, so it needs none of the compatibility code the public apps carry.
  • PostgreSQL and PostGIS
  • Docker
  • App Store Connect

Tagly

Two versions of the API live at once

Ran v1 and v2 of Tagly's REST API side by side, because an app already on someone's phone can't be updated on demand. Every request and response shape a shipped build relied on had to keep working, even as the database behind the API changed. A whole product area was deleted from the database while its old fields stayed supported for older builds. Only the API contract was versioned, so everything behind the contract could keep changing. Every piece of compatibility code carried a marker saying when it could be removed. Retiring v1 took three steps:

  • Built a minimum-version gate. Every request carries the app's version, and a build below the minimum is shown an update screen.
  • Raised the minimum version so every old install had to update, then waited out a safety window.
  • Deleted v1's endpoints, scanned for code that only v1 used, and dropped the database columns that only v1 wrote.

The whole cutover is written down as a repeatable procedure for the next version.

  • Express
  • TypeScript

Tagly

All types are defined once, shared across the apps and the server

Built a shared package that defines every endpoint's request and response shape. Every app and service imports the same package, so the client and the server always agree on the shape of the data. The package holds 160 endpoint contracts. The backend validates every incoming request against these contracts, and the apps' SDK checks every response and logs any mismatch.

  • Zod
  • TypeScript
  • Express

Tagly

English and Czech across the apps and the backend

Localised Tagly into English and Czech, from the apps to the backend's notification and email templates. Every user-facing string lives in a translation file, with English typed as the source, so a string missing from English is a type error. The language follows the device's language, and units follow the device's measurement system. Users can change both in their preferences.

  • i18next
  • React Native
  • TypeScript