Sharing a backend between web and mobile sounds straightforward. Build an API, point two clients at it, and keep the payloads in sync.
The difficult part is not transport. It is deciding what must be identical and what should be allowed to differ.
Share the product rules
Spicy Brain’s Laravel application is the system of record. It owns reminder resources, validation, authorization and the rules that determine what actions mean.
The Expo application consumes those resources directly. Its TypeScript contracts mirror the API rather than translating everything through a second local domain model.
That choice removes a layer of ceremony, but it also creates a responsibility: the API must express the product clearly. A mobile screen should not need to reverse-engineer whether a missing value means “not supported”, “not loaded” or “empty”.
Important (The boundary)
The server owns durable facts. The client owns presentation, interaction and temporary state.
Do not share the interface
The website uses Laravel, Inertia and Vue. The mobile app uses Expo Router and React Native. They share concepts, not components.
A desktop table can provide useful density. The same information on a phone may need a focused list, a bottom sheet and a larger touch target. Browser push is website-owned; native push registration belongs to the mobile application. Back behaviour, app lifecycle and notification actions all have platform-specific expectations.
Trying to erase those differences would move complexity into conditionals and weaken both experiences.
Keep state with its owner
The mobile application uses a deliberately boring state model:
- TanStack Query owns server resources and cache invalidation.
- An authentication provider owns the session.
- secure storage holds the token and cached identity.
- the theme provider owns appearance.
- forms keep temporary input locally.
There is no global store containing copies of server objects. After a reminder mutation, the relevant query-key family is invalidated so list and detail views converge on the backend again.
This matters when the application wakes from the background, follows a notification action or returns to a screen after another mutation. The answer to “where does this value come from?” remains short.
Preserve what the interface cannot edit
A reminder can belong to another record such as a property or bill. The current mobile form does not edit that association.
The easy mistake is to submit the visible fields and accidentally replace the unseen association with null. The correct behaviour is to preserve the existing values until the mobile product explicitly supports changing them.
This is a small example of an important rule: a simplified interface must not silently destroy information it chose not to show.
Native notifications are a lifecycle problem
Push delivery is only half of native notifications. The application also has to decide what happens when a person taps “complete” or “move to tomorrow” while the app is foregrounded, backgrounded or terminated.
Interactive categories must exist before use. Android channels must exist before permission is requested. Responses must be processed once, and only with an authenticated session. A response should be cleared after successful handling, not merely after being observed.
Those rules cross platform code, authentication and the reminder API. Keeping them outside individual screens prevents competing global handlers and makes failure easier to reason about.
Release safety belongs in the product
Production builds refuse insecure API URLs. Push registration fails closed when the app has not been connected to its deployment project. A release check verifies that native identifiers, the project identity and the production endpoint are real rather than placeholders.
These checks are not bureaucracy around the application. They stop a build that looks complete from shipping without the infrastructure its core feature needs.
The result
The web and mobile applications are clearly related, but neither is a poor imitation of the other. They agree on durable rules and resources, then use the interaction patterns of their own platform.
That is more work than wrapping the website. It also produces a product I can explain, test and evolve without pretending every screen lives in the same environment.