9 min read
How I created an app for VestCards (without knowing how to)

Mobile development has always been a gray area for me. Although I come from a React development background, developing and publishing an app is a very different experience. It came from a necessity: VestCards, a student flashcards platform, needed an app because students were constantly requesting it.

Pre-problems

To create this app, I would need a separate API. At that time, my server was just some built-in NextJS routes I used for the web app. I chose to develop my new server with Bun and Elysia, and the hosting was done on Fly.io.

It was the first time in my personal projects that I had to do such manual hosting: building Docker images, choosing deployment strategies (I learned about canary and blue-green deployments), and configuring everything myself. But honestly, it was worth it because of Fly.io’s low pricing.

This new server setup took some time, as I also migrated the web app to use these new API endpoints. I also migrated auth to Better-Auth because of the really good connector support for both React Native and React.

One of the best things about the Elysia + Bun ecosystem is this library called Eden, where you can have all your server API routes strongly typed with TypeScript. On the frontend, I could simply call something like:

api['v1'].auth.login(userEmail, userPassword)

It also has a lot of other cool features like middleware, automatic documentation, and a really simple developer experience. Since it runs on Bun, it is also really performant.

At this point, I was already realizing something interesting: mobile development was not just “frontend development but smaller”. It forced me to rethink the whole architecture of the project, especially around API boundaries, authentication flows, deployment, and versioning.

Tech Stack

Choosing React Native

I did some research about tech stacks for building this app: Kotlin Multiplatform, Swift, Flutter…

Since I already had a React background, I found that React Native was the best option because it checked some important boxes:

  • One codebase, multiple platforms.
  • Familiar stack — I found out that you can install many of the same libraries from React into a React Native app, so I could stick with third-party libraries I was already familiar with.
  • Use native components (later discovery) — Creating mobile apps with native components gives a much better UX and fluidity. It was not my first requirement, but I later realized it was a really good choice.

Although React Native is very similar to React, there are still many differences, like the router, the many different kinds of Views (ScrollView, FlatList, etc.), and the fact that building a mobile UI is completely different from building a desktop one.

Every tiny interaction matters more on mobile.

The mental model is also different. On the web, users usually tolerate more clutter and denser interfaces. On mobile, every detail matters. Simple things like spacing, touch targets, keyboard handling, and gesture interactions suddenly become very important.

Frameworks and More

As clarified before, I chose React Native. For the libraries, I ended up with:

  • Nativewind: a Tailwind-like library for React Native, although not all classes work

  • Lucide Icons: the same icon set the web app already uses

  • Tanstack Query: to control queries and mutations more efficiently

  • Expo: basically a framework that gives React Native some steroids 💉

    • It provides file-based routing, native APIs, animations, build tooling, and much more. You can even use it to compile and publish directly to the Play Store and App Store.

Expo was honestly one of the biggest reasons this project became viable for me.

Dev Experience

As a newcomer to the mobile development world, I was really thankful to have a MacBook Pro, because I quickly discovered that I could not test on an iOS simulator without one. The first steps were basically trying to install Android and iOS simulators and getting some code to run: AI helps a lot with these initial steps.

I stumbled into a problem really quickly: I was trying to have a monorepo with my web app, server (Bun + Elysia), and mobile app together. I found out the problem was related to React versions. Expo 50-something required React 19 while my web app was still running on React 18.

After trying everything: switching package managers, hoisting dependencies, forcing versions, and much more (for almost two weeks), I stumbled into some GitHub issues from people trying to do the exact same thing and having the same problems. Then I decided to go with a simpler approach: create a separate repository for vestcards-mobile and publish the shared code from the main repository as npm packages.

This was a huge tradeoff because I had to set up publishing CI pipelines, changelogs, package versioning, and all that infrastructure. But for mobile apps, having versioned shared packages actually makes a lot of sense because users can stay on older app versions for a long time.

flowchart LR
    subgraph Repo1["vestcards"]
        WEB["web-app"]
        API["api-server"]
        SHARED["@vestcards/shared"]
    end

    subgraph Repo2["vestcards-mobile"]
        MOBILE["expo-app"]
    end

    SHARED -->|"published as npm package"| MOBILE

In hindsight, this architecture ended up being much cleaner than forcing everything into a single monorepo setup.

One thing I underestimated was how much mobile development is about handling edge cases. On the web, most users share the same interaction model. On mobile, suddenly you have to care about:

  • safe areas
  • keyboard overlays
  • app background states
  • gestures
  • navigation stacks
  • offline behavior
  • dozens of tiny platform-specific quirks

React Native and Expo try really hard to abstract most of this away, unless you specifically want to deal with native details. This abstraction works surprisingly well most of the time, but sometimes you hit limitations.

One example was bottom sheets. At the time, the native Expo bottom sheet API only existed in beta and I did not want to upgrade everything just for that. So I ended up using Gorhom Bottom Sheet, which is still really good, but not fully native, so the app still has some gambiarras

Nevertheless, I was honestly surprised by how “native” the app felt. React Native has a reputation for being less performant than fully native apps, but with modern Expo tooling, native navigation, and proper animations, the experience felt incredibly smooth. Another thing I noticed is that debugging mobile apps feels way more chaotic than debugging web apps.

Sometimes the issue is:

  • JavaScript
  • native code
  • Metro cache
  • Gradle
  • Xcode
  • or literally something random

You end up learning a lot of platform knowledge by accident.

Five years ago, building and publishing a mobile app without prior experience would probably have taken me months of tutorials before writing anything useful.

AI also changed the way I read documentation. Instead of spending hours searching for the exact implementation details, I can now focus more on architecture and product decisions while using AI to accelerate repetitive setup and debugging tasks.

Publishing an App

Oh my god, what a headache. It was literally three weeks talking to Apple. They have very strict publishing policies.

My app is “premium user only” right now, meaning users buy their subscription on the web app and then use it to access the mobile app. Apple wanted me to implement IAP (In-App Purchases). I had to resubmit disclaimers explaining the business model and even participate in a virtual meeting before finally getting the app approved.

The Google Play Store had a completely different kind of challenge. You have to run a closed test with at least 12 people using your app for 14 days before you can release it publicly. Finding these testers was actually harder than I expected because most of my close friends and family use iPhones.

I eventually discovered this Reddit community where people exchange app testing specifically for Google Play requirements.

It helped me gather enough testers to hit the required number.

As of 26/05/2026, there are only 4 testing days remaining 🙏

Publishing an app made me realize that software engineering is often not even the hardest part.

Distribution, policies, approvals, compliance, and platform ecosystems can easily consume more time than actually building the product itself.

Conclusions

In hindsight, a flashcards product almost naturally belongs on mobile.

Students study:

  • on buses
  • between classes
  • during short breaks
  • before exams
  • while waiting somewhere

Mobile usage patterns fit spaced repetition perfectly. The first time I opened the app on my own phone and logged into a real VestCards account felt surreal.

Until that moment, it still felt like an experiment. Seeing my own production data rendered inside a native app made it feel like an actual product. I think the biggest thing this project taught me is that modern software development is much less about mastering a single technology and much more about being willing to continuously learn new systems.

A few years ago, the idea of building and deploying a mobile app alone would have felt impossible to me. Today, with modern frameworks, cloud tooling, open-source libraries, and AI assistance, the limiting factor is often just persistence.